https://wiki.freepascal.org/api.php?action=feedcontributions&user=Skalogryz&feedformat=atomFree Pascal wiki - User contributions [en]2024-03-28T12:32:45ZUser contributionsMediaWiki 1.35.6https://wiki.freepascal.org/index.php?title=Lazarus_InstantSearch&diff=156294Lazarus InstantSearch2023-04-19T20:17:32Z<p>Skalogryz: </p>
<hr />
<div>{{LanguageBar|Lazarus InstantSearch}}<br />
<br />
== Intro ==<br />
The instantsearch package is a package to search within sources (or other sources of text)<br />
<br />
It provides an as-you-type search mechanism. <br />
See the 'View - Instantsearch' menu item (or CTRL-ALT-F).<br />
<br />
It uses [https://manticoresearch.com/ Manticore Search] to provide the<br />
search capabilities. Manticore search creates an index of the source files,<br />
which it then uses to provide you with search capabilities.<br />
<br />
== Installation ==<br />
<br />
=== Install ManticoreSearch ===<br />
<br />
Before you can use Lazarus InstantSearch, you must have Manticore Search<br />
installed. It can be installed locally, or on another computer.<br />
<br />
To install Manticore search, please see the instructions at <br />
[https://manual.manticoresearch.com/Installation]<br />
<br />
=== Install InstantSearch ===<br />
<br />
Using the package menu, install the '''lazinstantsearch''' package.<br />
You will need to recompile the IDE. <br />
<br />
=== Configure instantsearch. ===<br />
<br />
After installing the package, before you can use instantsearch, it needs to be configured: <br />
at least the location of the manticore search engine must be specified.<br />
<br />
Using Tools - options, select the 'Instant Search' item under the Environment<br />
option group.<br />
<br />
== Configuration ==<br />
The configuration is done using the 'Instant Search' item under the Environment option group.<br />
<br />
[[File:lazarus_instantsearch_options1.png|Options]]<br />
<br />
You must configure at least the following things:<br />
* Protocol - MySQL or HTTP<br />
* MySQL Client version - if you selected the MySQL protocol.<br />
<br />
You can leave hostname and port empty if you installed on the local machine<br />
using the default settings.<br />
<br />
Test the connection with the 'Test connection' button.<br />
<br />
If the connection is OK, set the index name to use, and use the 'Create<br />
Index' button to create the table used to store the search index.<br />
<br />
if the index already exists, you will be warned about this.<br />
<br />
When the index has been created (or was already created), <br />
the configuration dialog will check which source trees exist (normally<br />
none), and will offer to start indexing. This is done in the background.<br />
<br />
=== Configuring source trees ===<br />
<br />
By default, InstantSearch configures 4 source trees:<br />
* RTL : The RTL sources of FPC.<br />
* FCL : The packages sources of FPC.<br />
* Compiler : The FPC compiler sources <br />
* LCL : The lazarus LCL sources.<br />
<br />
You can define as much search trees as you want, using the buttons in the toolbar.<br />
You can use the buttons to refresh the indexes of a source tree.<br />
<br />
Note: when you change the source directory of FPC or rescan it, <br />
the RTL/FCL/Compiler trees will be re-indexed.<br />
<br />
[[File:lazarus_instantsearch_options2.png|Source tree definitions]]<br />
<br />
The 4 trees are configured and indexed automatically, a message will appear<br />
in the messages window when this happens.<br />
<br />
=== Indexing the current (active) project. ===<br />
The sources of the current project can also be searched, provided it has<br />
been indexed. This is a 2-step process:<br />
<br />
* Mark a project as indexable. This can be done using the ``Mark project as indexable``<br />
: popup menu item in the project inspector, or below the 'Project' menu in the main menu. <br />
<br />
* Actually index the project. The project is automatically indexed when you<br />
: mark it as indexable. <br />
: You can manually index a project at any time using the project menu item in the main menu, <br />
: or the popup item. <br />
<br />
== Searching ==<br />
To search, open the 'Instant search' window under 'View' (or press CTRL-Alt-F)<br />
<br />
If Instant Search was not not configured, you will get a warning and the IDE<br />
will offer to open the configuration dialog.<br />
<br />
The dialog will automatically copy the selection (provided it is not too big and does not contain CR/LF characters) <br />
to the search edit box and do a search with that. <br />
<br />
You can type in the search edit box at the top.<br />
As you type, the search results will be more refined or expanded.<br />
<br />
Each match is displayed with the line contents, and the filename+lineno.<br />
<br />
[[File:lazarus_instantsearch_searching.png|Searching]]<br />
<br />
Double click one of the lines to open the file at the selected location.<br />
<br />
If you have too many matches, you can check or uncheck some of the source<br />
trees in the top-right search bar.<br />
[[Category:Indexing]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Phonetic_Algorithm&diff=156293Phonetic Algorithm2023-04-19T20:16:58Z<p>Skalogryz: </p>
<hr />
<div>[http://en.wikipedia.org/wiki/Phonetic_algorithm Wikipedia] A phonetic algorithm is an algorithm for indexing of words by their pronunciation. Most phonetic algorithms were developed for use with the English language; consequently, applying the rules to words in other languages might not give a meaningful result. <br />
<br />
* SoundEx - function is part of StrUtils unit<br />
* Metaphone / Double Metaphone (Metaphone2) implementation - at [http://sourceforge.net/projects/metaphone/ DelphiMetaphone]. LGPL 2.0 license.<br />
<br />
[[Category:Libraries]]<br />
[[Category:Indexing]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:Indexing&diff=156292Category:Indexing2023-04-19T20:16:45Z<p>Skalogryz: Created page with "anything related to data indexing and search engines"</p>
<hr />
<div>anything related to data indexing and search engines</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=ioda&diff=156291ioda2023-04-19T20:16:29Z<p>Skalogryz: /* Download */</p>
<hr />
<div>== About ==<br />
<br />
ioda is a fulltext database: a word indexing and retrieving engine. It stores unique words from a file or database source in a btree and their repeats in an flexible and highly space optimized list structure. Each stored word "knows" its source, position in the source and some (optional) info bytes.<br />
<br />
We use the term "database" for the summary of all files of an ioda data collection. I.e. if you have indexed your webserver's HTML files in an ioda database called "myserver", at the very least these ioda files make the database: myserver.config, myserver.btf, myserver.ocl and eventually myserver.ref. The only file you have to edit manually is the config file, where you describe the properties of the database. There can be some more helper files.<br />
<br />
== Features ==<br />
<br />
=== Master or Slave ===<br />
<br />
ioda can be used standalone ("master mode") for archiving files. In this case it stores full file names and can archive whole directory trees - i.e. the whole webserver content - by one call. On the other hand, ioda can be used as an addon to an existing (i.e. SQL) database in "slave mode" and store the unique key of each database record as a reference to its words.<br />
<br />
=== Logical Operators ===<br />
<br />
For retrieving information, ioda handles logical operators (AND, OR, NOT, NEAR), parenthesis and optional word distance values (ie. AND.4). NEAR is an operator which means AND.50. The query parser of ioda is able to optimize a search path for complex queries like "(Albert or Alfred) and.1 Einstein) and Quant* not Physik*".<br />
<br />
=== Wildcards and Regular Expressions ===<br />
<br />
Beginning in Release 1.3, ioda can retrieve data with wildcards or regular expressions. I.e.: The word "barfooter" will be found with the query /foo/. This is similar to the wildcard notation *foo*. ioda internally converts wildcards mostly into regular expressions.<br />
<br />
=== Delete and Update Functions ===<br />
<br />
ioda can delete entries and update them by deleting the old version and inserting the new one. (Entries means the list of words from an article, a file etc.). ioda offers a merge function for merging two databases into one or for optimization purposes. In the last case, an existing database will be rebuilt with continuous word lists (which are impossible to create in the orginal archiving run without wasting much disk space).<br />
<br />
=== Sorting by Relevance ===<br />
<br />
There are some more features: ioda can sort hits by time (of file or database entry) or by weight. In the last case words (or combinations while using the AND operator) are appraised by their position in the text. ioda can (optionally) detect text doublettes using MD5 checksums and can ignore them or store them in a space optimized way.<br />
<br />
=== Charsets ===<br />
<br />
ioda can handle all ISO-8859-XX charsets and UTF-8. In the case of ISO charsets ioda can handle the casefolding (optional automatic uppercase function). When using UTF-8 the calling application has to handle all casefoldings.<br />
<br />
=== Flexible Indexing through external Filters ===<br />
<br />
For archiving whole directory trees, ioda needs the support of an external program. This can be written in any language and may work as a pipeline or may generate temporary files. ioda can store additional information on each word. Besides the mandatory information (source id, source position and a 16-bit-value for flags and other informations), each word can optionally have a timestamp and a 32-bit-value (instead of the 16-bit one).<br />
<br />
=== Tailor-made Data Structures ===<br />
<br />
The database structure of ioda consists of two or three parts, which are all designed by the author (non standard):<br />
<br />
* The Bayer Baum, BTree, (*.btf): It stores all unique words, each pointing to...<br />
* The Word occurrence list (*.ocl): It stores information about the words, at least the file or database id (ie. unique key) as doubleword, the position (in word counts) in a word, the weight and an optional info byte. This can store information like "word is in title" or something else. ioda offers bigger data models for the occurrence list, ie. for storing a timestamp in each word or a source information. This bigger structure is mainly used for ioda standalone duties.<br />
* The File reference list (*.ref) is used for standalone service only. In this case, ioda manages the ids itself ("master mode") and the ids point to the entries in the fileref list (instead of getting ids from a master database). In the fileref list, a full path name is stored. It is possible to agree upon a base path at creating time of the ioda database which is a leading part of the full path and can be truncated (ie. a webserver root path) to avoid redundant information.<br />
<br />
=== Interfaces ===<br />
<br />
From the source, four binaries can be built:<br />
<br />
* ioda as a command line program (joda)<br />
* ioda as a server for client/server communicating over TCP sockets (jodad)<br />
* ioda as a linkable library (libjodafulltext.so). Interfaces to C, Perl, Python and PHP are published in the source package<br />
* ioda as a CGI program. This is only a trunc which does no HTML-formatting<br />
<br />
== Example ==<br />
<br />
ioda is in a production environment ie. as full text index to a Wikipedia mirror: http://lexikon.rhein-zeitung.de. Try a query with wildcard (*) to force a search or use this query:<br />
<br />
((Albert or Alfred) and.1 Einstein) and /^Quant.+sprung/) not Schrödinger<br />
<br />
== Compiling and Installing ==<br />
<br />
You can use the binares from the bin package immediatly under Linux. For compiling the sources, a Makefile is available in the source package. If you want to use the Perl and/or Python or PHP import modules, please install the source or the binary package first! To install all, you can extract the source package into one subdirectory. First call "make", then "make install" from the master Makefile to do all in one. The Free Pascal Compiler ≥ 1.9.3 is needed (recent version is 2.0). Important: Switch the Delphi mode in the fpc config file on (-S2)! No other libraries are required for the binaries. At the moment, it is only guaranteed that it runs under Linux. Under Windows, we have only tested read only until now. Theoretically it will be no or only little work to adapt ioda for all other OS which are supported by Free Pascal.<br />
<br />
== Download ==<br />
<br />
Homepage: http://ioda.sourceforge.net/<br />
<br />
[[Category:FPC]]<br />
[[Category:Networking]]<br />
[[Category:Databases]]<br />
[[Category:Indexing]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=OpenXR&diff=156150OpenXR2023-03-28T05:00:26Z<p>Skalogryz: </p>
<hr />
<div>OpenXR is an open, royalty-free standard for access to virtual reality and augmented reality platforms and devices. It is developed by a working group managed by the Khronos Group consortium. OpenXR was announced by the Khronos Group on February 27, 2017 during GDC 2017<br />
==Pascal Headers==<br />
The OpenXR headers ported to pascal<br />
* https://github.com/dedm0zaj/simple_openxr_freepascal <br />
<br />
[[Category:VR]]<br />
[[Category:OpenXR]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=OpenXR&diff=156149OpenXR2023-03-28T04:59:38Z<p>Skalogryz: Created page with "The OpenXR headers ported to pascal * https://github.com/dedm0zaj/simple_openxr_freepascal Category:VR"</p>
<hr />
<div>The OpenXR headers ported to pascal<br />
* https://github.com/dedm0zaj/simple_openxr_freepascal <br />
<br />
[[Category:VR]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154262RichMemo/FAQ2022-09-29T19:04:24Z<p>Skalogryz: /* Add text with font */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use '''RichUtilsUtils''' which comes with RichMemo and call '''InsertFontText''' function<br />
<source lang="delphi"><br />
uses ... RichUtilsUtils ..<br />
<br />
..<br />
<br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</source><br />
<br />
:RichMemo's base API has the action of inserting the text and applying the style separated.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154261RichMemo/FAQ2022-09-29T19:04:12Z<p>Skalogryz: /* Add text with font */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use '''RichUtilsUtils''' which comes with RichMemo and call '''InsertFontText''' function<br />
<source lang="delphi"><br />
uses ... RichUtilsUtils ..<br />
<br />
..<br />
<br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</source><br />
<br />
:RichMemo's base APIs itself have the action of inserting the text and applying the style separated.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154260RichMemo/FAQ2022-09-29T19:03:59Z<p>Skalogryz: /* Add text with font */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use '''RichUtilsUtils''' which comes with RichMemo and call '''InsertFontText''' function<br />
<source lang="delphi"><br />
uses ... RichUtilsUtils ..<br />
<br />
..<br />
<br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</source><br />
<br />
RichMemo's base APIs itself have the action of inserting the text and applying the style separated.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154259RichMemo/FAQ2022-09-29T19:02:40Z<p>Skalogryz: /* Add text with font */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use RichUtilsUtils which comes with RichMemo and call InsertFontText function<br />
<source lang="delphi"><br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</source><br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154258RichMemo/FAQ2022-09-29T19:02:20Z<p>Skalogryz: /* Add text with font */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use RichUtilsUtils which comes with RichMemo and call InsertFontText function<br />
<source syntax="delphi"><br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</source><br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154257RichMemo/FAQ2022-09-29T19:01:40Z<p>Skalogryz: /* Don't show horizontal bar */</p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
===Add text with font===<br />
:Q: how i can add text or line richmemo with font or textstyle?<br />
:A: The fastest way is to use RichUtilsUtils which comes with RichMemo and call InsertFontText function<br />
<code syntax="delphi"><br />
var<br />
prms : TFontParams;<br />
begin<br />
prms := GetFontParams(RichMemo1.Font);<br />
prms.Color := clBlue;<br />
prms.Style := [fsBold];<br />
// this adds the blue and bolded Hello World at the end of the richmemo<br />
// it's possible to pass the 4th parameter to the function to specify <br />
// where the text should be added. By default - at the end.<br />
InsertFontText(RichMemo1,'hello world', prms);<br />
end;</code><br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154256RichMemo/FAQ2022-09-29T18:55:03Z<p>Skalogryz: </p>
<hr />
<div>Forum Asked Questions<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154255RichMemo/FAQ2022-09-29T18:54:49Z<p>Skalogryz: /* Don't show horizontal bar */</p>
<hr />
<div>The section covers questions that are often seen on the forum<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal scroll bar''' might not show up if '''WordWrap''' is enabled.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo/FAQ&diff=154254RichMemo/FAQ2022-09-29T18:54:38Z<p>Skalogryz: Created page with "The section covers questions that are often seen on the forum ===Don't show horizontal bar=== :Q: I started to work with richmemo but don't show me horizontal bar only vertic..."</p>
<hr />
<div>The section covers questions that are often seen on the forum<br />
<br />
===Don't show horizontal bar===<br />
:Q: I started to work with richmemo but don't show me horizontal bar only vertical bar even i typing or loadfile, in properties i select SSboth on srcollbars.<br />
:A: '''Horizontal''' bar might not show up if '''WordWrap''' is enabled.<br />
<br />
==See Also==<br />
* [[RichMemo]]<br />
[[Category:RichMemo]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=RichMemo&diff=154253RichMemo2022-09-29T18:52:40Z<p>Skalogryz: /* See also */</p>
<hr />
<div>{{RichMemo}}<br />
[[Image:richmemosample.png|thumb|RichMemo macOS screenshot (thanks to Dominique Louis)]]<br />
==Introduction==<br />
'''RichMemo''' is a package that includes a component to replace the Delphi TRichEdit component. It's designed in cross-platform way, so implementation is possible for the following platforms: Win32, MacOSX and Linux. Since, being [[#Platform Support Matrix|cross-platform]] is the primary target, the native RichMemo API can be [[#(Delphi) RichEdit like interface|extended]] to make it Delphi's RichEdit compatible.<br />
<br />
Its main characteristics are <br />
* Text highlight<br />
* System based Unicode edit support<br />
<br />
Planned: (patches are welcomed) <br />
* Adding images into text<br />
* Embedding LCL Controls?<br />
<br />
The download contains the component, an installation package and a demo application, that illustrates the features of the component along with some instrumentation for evaluating the chart on a given system.<br />
<br />
Please add your bugreports, feature requests, pull request on [https://github.com/skalogryz/richmemo/issues GitHub].<br />
<br />
== Licensing ==<br />
Author: [[User:Skalogryz|Dmitry 'skalogryz' Boyarintsev]]<br />
<br />
License: [http://svn.freepascal.org/svn/lazarus/trunk/COPYING.modifiedLGPL modified] [http://svn.freepascal.org/svn/lazarus/trunk/COPYING.LGPL LGPL] (the same as the FPC RTL and the Lazarus LCL). You can contact the author if the modified LGPL doesn't work with your project licensing.<br />
<br />
== Download ==<br />
The easiest way to install the stable version of the component is via the [[Online Package Manager]] included in current Lazarus versions.<br />
<br />
The latest trunk is available on GitHub: https://github.com/skalogryz/richmemo/ . The latest sources can be downloaded by [https://github.com/skalogryz/richmemo/archive/refs/heads/master.zip this GitHub link].<br />
<br />
== Installation ==<br />
* Download the package set.<br />
* Open '''richmemo_design.lpk''' package, and install it, rebuilding the IDE.<br />
* TRichMemo is added to the 'Common Controls' component page.<br />
<br />
[[Image:RichMemoPalette.PNG]]<br />
<br />
Originally '''richmemopackage''' was a single file, containing IDE specific and run-time needed classes.<br />
However, that meant that IDEIntf package would be a requirement for any project using RichMemo.<br />
In order to mitigate that the package was split into to separate packages:<br />
* richmemopackage - run-time rich memo component.<br />
* richmemo_design - IDE extensions to support for RichMemo component.<br />
<br />
== Font Parameters ==<br />
Font parameters are generally represented by TFontParams record.<br />
The type is used to describe '''rich''' attributes of the font. Some attributes goes beyond the scope of TFont class, thus an extra type was necessary. However, most of the TFont related data types are reused (i.e. TFontStyles).<br />
* Name - name of the font (family). <br />
* Size - size of the font in points (passing a negative value might produce unexpected results).<br />
* Color - color of the font.<br />
* Style - font styles, including Bold, italic, strike-through, underline.<br />
* HasBkClr - boolean flag, if the text for the should have background color (if true, BkColor field is used, otherwise BkColor is ignored). Transparency is not supported.<br />
* BkColor - background (aka highlight) color for the text.<br />
* VScriptPos - vertical adjustment for the text [[Image:richmemo_Subsuper.png|right]].<br />
** vpNormal - no special position.<br />
** vpSubscript - the text is subscript. <br />
** vpSuperscript - the text is superscript. <br />
:The actual vertical offset of super/sub scripts depends on OS implementation and currently cannot be controlled.<br />
<br />
You can assign FontParams from TFont structure. For that you'll need to use <tt>GetFontParams(afont: TFont)</tt> function. Note, most of LCL controls have TFont set to "default" values. These are not actual values, but rather an indication that control's default font should be used (similar to crDefault for TColor). <br />
<br />
GetFontParams function resolves "Default" font name and returns the actual font family name.<br />
<br />
== Methods ==<br />
=== SetTextAttributes ===<br />
<source lang="delphi">procedure SetTextAttributes(TextStart, TextLen: Integer; AFont: TFont);</source><br />
* TextStart : Integer - the first character to be modified<br />
* TextLen : Integer - number of characters to be modified<br />
* AFont : TFont - a font that should be applied to the part of the text<br />
<br />
<source lang="delphi">procedure SetTextAttributes(TextStart, TextLen: Integer; const TextParams: TFontParams);</source><br />
* TextStart : Integer - the first character to be modified<br />
* TextLen : Integer - number of characters to be modified<br />
* TextParams : TFontParams - font parameters to be set<br />
<br />
SetTextureAttributes methods are changing specified text range style. Font parameters is passed in both methods, by AFont parameter (LCL TFont object) or by TFontParams (declared at RichMemo).<br />
<br />
Setting text attributes does not change current selection. If it's necessary to modify the style of currently selected text, you should SelStart and SelLength as a text range values:<br />
<source lang="delphi">RichMemo1.SetTextAttributes(RichMemo1.SelStart, RichMemo1.SelLength, FontDialog1.Font);</source><br />
<br />
=== GetTextAttributes ===<br />
<source lang="delphi">function GetTextAttributes(TextStart: Integer; var TextParams: TFontParams): Boolean; virtual;</source><br />
* TextStart : Integer - the character position to be queried for font parameters<br />
* var TextParams : TFontParams - output value, filled with charachter's font attributes. If the method fails and returns false, record's field values is undefined.<br />
<br />
Fill font params of the character, at TextStart position. <br />
Method returns True if textstart is valid character position, and False overwise.<br />
<br />
=== GetStyleRange ===<br />
<source lang="delphi">function GetStyleRange(CharPos: Integer; var RangeStart, RangeLen: Integer): Boolean; virtual;</source><br />
<br />
Returns a range of characters that share the same font parameters, i.e. all characters in the range has the same font name, size, color and styles. <br />
<br />
* CharPos : Integer - a character that belongs to the style range. It's not necessary for this position to be at the begining on the style range. It can be in the middle on in the end of the style range. The first character position is returned by RangeStart parameter.<br />
* var RangeStart : Integer - the first character in the range<br />
* var RangeLen : Integer - number of characthers in the range<br />
<br />
The method returns true if successed. the method returns false, if CharPos is incorrect value - more than available characters or some other error.<br />
=== CharAtPos ===<br />
<source lang="delphi">function CharAtPos(x,y: Integer): Integer; virtual;</source><br />
<br />
Returns zero-based character offset (not UTF8 position). Returns -1 if fails.<br />
<br />
* x, y - is a point in the client area of the RichMemo control. <br />
<br />
The method is aware of scrolling state of the control. Thus two calls to CharAtPos(0,0) might return different values if scroll positions is changed between calls.<br />
See "examples/hittest" for the sample of usage.<br />
<br />
If specified x,y would be out of the content of RichMemo the return value is undefined. It is left up to widgetset to either return -1 or closes character available.<br />
<br />
=== SetRangeColor ===<br />
<br />
<source lang="delphi">procedure SetRangeColor(TextStart, TextLength: Integer; FontColor: TColor);</source><br />
<br />
The method sets color of characters in the specified range to the FontColor. Other font parameters (name, size, styles) are left unchanged. <br />
<br />
* TextStart: Integer - the first character in the range<br />
* TextLength: Integer - number of characters in the range<br />
* FontColor: TColor - color that should be set<br />
<br />
=== SetRangeParams ===<br />
<source lang="delphi"><br />
procedure SetRangeParams(TextStart, TextLength: Integer; ModifyMask: TTextModifyMask;<br />
const fnt: TFontParams; AddFontStyle, RemoveFontStyle: TFontStyles); overload;<br />
</source><br />
<br />
The method changes font parameters in the specified range.<br />
* TextStart - the first character in the range<br />
* TextLength - number of characters in the range <br />
* ModifyMask - shows what exact font attributes must be updated The mask accepts any combination of the following values:<br />
** tmm_Color - font color would be modified (fnt.Color field must be populated)<br />
** tmm_Name - font name would be modified (fnt.Name field must be populated)<br />
** tmm_Size - font size would be modified (fnt.Size field must be populated)<br />
** tmm_Styles - font styles would be modified as specified by AddFontStyle, RemoveFontStyle parameters <br />
** tmm_BackColor - text highlight (background) color would be modified. (fnt.HasBkClr and fnt.BkColor fields must be populated )<br />
** Sending an empty mask will cause the method to return without making any changes.<br />
* fnt - font parameters that are going to be used, depending on the ModifyMask values.<br />
* AddFontStyle - set of styles that should be applied to the range (used only if tmm_Styles in ModifyMask, otherwise ignored)<br />
* RemoveFontStyle - set of font style should should be removed from the range (used only if tmm_Styles in ModifyMask, otherwise ignored)<br />
<br />
<source lang="delphi">procedure SetRangeParams(TextStart, TextLength: Integer;<br />
ModifyMask: TTextModifyMask; const FontName: String; <br />
FontSize: Integer; FontColor: TColor; <br />
AddFontStyle, RemoveFontStyle: TFontStyles);</source><br />
<br />
The overloaded version of the method. It's just a wrapper around the parameter using TFontParams structure. <br />
The method doesn't support changing of background color, you should use the TFontParams version<br />
* FontName - font name that should set (used only if tmm_Name in ModifyMask, otherwise ignored)<br />
* FontColor - font color that should set (used only if tmm_Color in ModifyMask, otherwise ignored)<br />
<br />
For example, if the following code is used<br />
<source lang="delphi"><br />
RichMemo1.SetRangeParams ( <br />
RichMemo1.SelStart, RichMemo1.SelLength,<br />
[tmm_Styles, tmm_Color], // changing Color and Styles only<br />
'', // this is font name - it's not used, thus we can leave it empty<br />
0, // this is font size - it's font size, we can leave it empty<br />
clGreen, // making all the text in the selected region green color<br />
[fsBold, fsItalic], // adding Bold and Italic Styles<br />
[]<br />
);<br />
</source><br />
<br />
===GetParaAlignment===<br />
Gets paragraph alignment<br />
<source lang="delphi">function GetParaAlignment(TextStart: Integer; var AAlign: TParaAlignment): Boolean; </source><br />
* TextStart - the position of the character that's belongs to the paragraph<br />
* AAlign - gets alignment of a paragraph.<br />
'''Note''': <br />
* Win32 - full-width Justification doesn't work on Windows XP and earlier.<br />
* OSX - due to Carbon limitation, this is not working in Carbon, but works for Cocoa widgetset.<br />
===SetParaAlignment===<br />
Sets paragraph alignent<br />
<source lang="delphi">procedure SetParaAlignment(TextStart, TextLen: Integer; AAlign: TParaAlignment); </source><br />
'''Note''': <br />
* Win32 - full-width Justification doesn't work on Windows XP and earlier.<br />
* OSX - due to Carbon limitation, this is not working in Carbon, but works for Cocoa widgetset. It's also need to loaded that the correct alignment would be loaded from RTF file<br />
<br />
===GetParaMetric===<br />
Returns paragraph metrics for a given paragraph<br />
[[Image:richmemo_parametric.PNG|right]]<br />
* FirstLine - an offset for the first line (in points), of the paragraph from the beginning of control<br />
* TailIndent - an offset for each line (in points), except for the first line, of the paragraph from the end of the control<br />
* HeadIndent - an offset for each line (in points) of the paragraph from the end of the control<br />
* SpaceBefore - additional space before paragraph (in points)<br />
* SpaceAfter - additional space after paragraph (in points)<br />
* LineSpacing - a factor used to calculate line spacing between lines in the paragraph. The factor is applied to Font size (tallest in the line), not the line height. Thus it's matching CSS's line-height property. Note that normal line-spacing is 1.2. I.e. font of 12 pt size, an actual lines spacing set to 1.2, would result in 14pt line height. <br />
** not supported in WinXP or earlier<br />
** most of the system would not allow if linespacing is set to less than 1.2. The value would be ignored and they'd default to 1.2<br />
<br />
'''Note''' <br />
* RichEdit paragraph settings are specified in pixels, not points, so you'll need to either convert these sizes yourself or use RichMemoHelper methods<br />
* the notation of "Left"/"Right" offsets is avoided to prevent confusion for RTL.<br />
<source lang="delphi">function GetParaMetric(TextStart: Integer; var AMetric: TParaMetric): Boolean; </source<br />
<br />
===SetParaMetric===<br />
Sets paragraph metrics<br />
<source lang="delphi">procedure SetParaMetric(TextStart, TextLen: Integer; const AMetric: TParaMetric); </source><br />
<br />
It's recommended to use InitParaMetric function in order to initialize TParaMetric structure. Otherwise zeroing structure out is by it size (i.e. FillChar(m, sizeof(TParaMetric), 0) is sufficient. If TParaMetric values are received from GetParaMetric call, then the structure is considered to be initialized properly.<br />
<br />
===GetParaRange===<br />
Returns character range for a paragraph. The paragraph is identified by a character.<br />
<source lang="delphi"><br />
function GetParaRange(CharOfs: Integer; var ParaRange: TParaRange): Boolean; <br />
function GetParaRange(CharOfs: Integer; var TextStart, TextLength: Integer): Boolean;<br />
</source><br />
CharOfs - is a character that belongs to a paragraph which range is returned.<br />
TParaRange - is a structure that returns 3 fields<br />
* start - the first character in the paragraph<br />
* lengthNoBr - the length of the paragraph, excluding the line break character<br />
* length - the length of the paragrpah, including the line break, if present the last line in the control doesn't contain a line break character, thus length = lengthNoBr value.<br />
<br />
===SetParaTabs===<br />
Sets the set of tab stops to paragraphs in the specified length.<br />
<source lang="delphi"><br />
procedure SetParaTabs(TextStart, TextLen: Integer; const AStopList: TTabStopList); </source><br />
* TextStart<br />
* TextLen<br />
* AStopList - the structure that contains an array of tab stops. <br />
** Count - specifies the number of initialized elements. <br />
** Tabs - array contains the description of each tab stop, consisting of an Offset and Alignment. <br />
*** Offset - the tab point offset in points (from the left side of the control)<br />
*** Align - the alignment of the tab stop (tabLeft - default one, tabCenter, tabRight, tabDecimal).<br />
'''Notes'''<br />
;Renamefest: Prior to r4140 TTabAlignment enum values were taLeft, taCenter, taRight, taDecimal,but it did collide with Classes.TAlignment declarations. Thus the suffix was changed.<br />
;win32:allows no more that 32 tabs<br />
;gtk2:only tabLeft align is supported<br />
;carbon:not implemented<br />
<br />
===GetParaTabs===<br />
Gets the tab stops array. If no specific tabs were specified (widget default tab stops) AStopList count would be set to 0.<br />
<source lang="delphi"><br />
function GetParaTabs(CharOfs: Integer; var AStopList: TTabStopList): Boolean; <br />
</source><br />
<br />
===SetParaNumbering===<br />
Sets paragraphs automatic numbering and styles, such as bullets<br />
<source lang="delphi"><br />
procedure SetParaNumbering(TextStart, TextLen: Integer; const ANumber: TParaNumbering); virtual;<br />
</source><br />
* '''TextStart''' - the character of the the first paragraph to apply the style too<br />
* '''TextLength''' - the number of characters that paragraph properties should be modified<br />
* '''TParaNumbering''' - the structure describing the paragraph numbering information<br />
:* '''style''' - the style of numbering<br />
::Note: for cross-platform compatibility, it's recommended to use either '''pnNone''', '''pnBullet''', '''pnNumber''' or '''pnCustomChar'''. RTF provides more support for numbering, yet not every platform rich-text widgetset supports them<br />
:*'''Indent''' - offset in points <br />
:*'''CustomChar''' - custom character to be used as a "bullet" for each paragraph. Note, it's a WIDECHAR not UTF8<br />
:*'''NumberStart''' - the initial number in numbering of paragraphs. Use for '''pnNumber''' only. Used to support "split" numbered lists<br />
:*'''SepChar'''<br />
:*'''ForceNewNum''' - if true and Style is pnNumber, NumberStart is used for the new numbering<br />
:for initializing the structure you can use InitParaNumbering() or InitParaNumber() functions. Also using Default() would work too<br />
<br />
===SetRangeParaParams===<br />
Sets paragraph metrics selectively<br />
<source lang="delphi">procedure SetRangeParaParams(TextStart, TextLength: Integer; ModifyMask: TParaModifyMask; const ParaMetric: TParaMetric);<br />
</source><br />
* TextStart - the character of the the first paragraph to apply the style too<br />
* TextLength - the number of characters that paragraph properties should be modified<br />
* ModifyMask - masks the identifies what para metric should be set for each paragraph within TextStart..TextLength block. Other characters will be left unmodified. Members of the set are<br />
** pmm_FirstLine - changes the first line indentation. FirstLine field of ParaMetric must be initialized<br />
** pmm_HeadIndent - changes the heading paragraph indentation. HeadIndent field of ParaMetric must be initialized <br />
** pmm_TailIndent - changes the trail paragraph indentation. TailIndent field of ParaMetric must be initialized<br />
** pmm_SpaceBefore - changes the space preceding . SpaceBefore field of ParaMetric must be initialized<br />
** pmm_SpaceAfter - changes the space after (below) the paragraph. SpaceAfter field of ParaMetric must be initialized<br />
** pmm_LineSpacing - line spacing within paragraph. LineSpace field of ParaMetric must be initialized<br />
* ParaMetric - the structure containing the values to be set. Only fields specified by ModifyMask should be initialized<br />
<br />
=== LoadRichText ===<br />
<source lang="delphi">function LoadRichText(Source: TStream): Boolean; virtual;</source><br />
<br />
* Source: TStream - a stream to read richtext data from<br />
<br />
The method loads RTF enocded data from the specified stream. Returns true if success, and false otherwise. If source is nil, the method returns false<br />
<br />
The content of TRichMemo is completely replaced by the content on the source stream. Current text selection is reset.<br />
<br />
=== SaveRichText ===<br />
<br />
<source lang="delphi">function SaveRichText(Dest: TStream): Boolean; virtual;</source><br />
<br />
* Source: TStream - a stream to read richtext data from<br />
<br />
The method saves RTF enocded data to the specified stream. Returns true if success, and false otherwise. If source is nil, the method returns false<br />
<br />
Current state of TRichMemo is unchanged after the method returns<br />
===Search===<br />
<source lang="delphi">function Search(const ANiddle: string; Start, Len: Integer; const SearchOpt: TSearchOptions): Integer;</source><br />
* ANiddle: string - a text to search for<br />
* Start: Integer - a character position to start search from, if -1 or 0 search from the start<br />
* Len: Integer - length (in characters, not bytes) of the text to search for the niddle.<br />
* SearchOpt: TSearchOptions <br />
** soMatchCase - case sensitive search<br />
** soBackward - searching from the end of the document to the character identified by "Start" parameter<br />
** soWholeWord - searching for the whole word <br />
<br />
The method returns the character position of the found sub-string. The position is suitable for use in SelStart property. However, might not be usable for the direct Copy operation, if Unicode characters are present in the code. Instead UTF8Copy should be used.<br />
<br />
If the ANiddle was not found in the text, -1 is returned<br />
<br />
<source lang="delphi">function Search(const ANiddle: string; Start, Len: Integer; const SearchOpt: TSearchOptions;<br />
var TextStart, TextLength: Integer): Boolean;</source><br />
(The method is introduced with r5115)<br />
* ANiddle: string - a text to search for<br />
* Start: Integer - a character position to start search from, if -1 or 0 search from the start<br />
* Len: Integer - length (in characters, not bytes) of the text to search for the niddle.<br />
* SearchOpt: TSearchOptions <br />
** soMatchCase - case sensitive search<br />
** soBackward - searching from the end of the document to the character identified by "Start" parameter<br />
** soWholeWord - searching for the whole word <br />
* (output) ATextStart - a position of the text found (in characters)<br />
* (output) ATextLength - length of the text found (in characters (cursor positions!))<br />
The method returns the true if searched sub-string is found, false otherwise. <br />
<br />
The returned ATextStart and ATextLength values are suitable for use in SelStart and SelLength properties. <br />
<br />
'''Note''': for complex scripts, the found text might be exactly the same as the sub-string searched. The search behavior (on complex scripts) is widgetset specific. Thus searching for the same string on different widgetsets (i.e. win32 or gtk2) might produce different results. If you need the same results and don't get them, please create a bug report.<br />
<br />
<br />
If you need to extract the found text, you should use GetText() method (passing ATextStart and ATextLength values).<br />
===GetText, GetUText===<br />
<source lang="delphi"><br />
function GetText(TextStart, TextLength: Integer): String;<br />
function GetUText(TextStart, TextLength: Integer): UnicodeString;<br />
</source><br />
* TextStart: Integer - a character position to start extract from. (0 - is the first character in the text). <br />
* TextLength: Integer - length (in characters, not bytes) of the text to extract<br />
<br />
GetText() returns UTF8 sub-string <br />
<br />
GetUText() returns UTF16 sub-string <br />
<br />
Current selection would not be affected by the operation. (If you see the selection affected, please report as an issue).<br />
<br />
You should not consider the efficiency of either method. For example, WinAPI internally operates with UTF16 characters, thus GetUText() '''might''' be more efficient for it. While Gtk2 operates UTF8 and calling GetText() '''might''' be more efficient for it. Instead of thinking about underlying system, you should be considering the needs of your task.<br />
===Redo===<br />
<source lang="delphi"><br />
procedure Redo;<br />
</source><br />
<br />
The method redos the last change [http://lazarus-ccr.sourceforge.net/docs/lcl/stdctrls/tcustomedit.undo.html undone] previously.<br />
<br />
You can always call [[#CanRedo|CanRedo]] to check, if there're any actions can be redone.<br />
<br />
== Properties ==<br />
<br />
=== ZoomFactor === <br />
<source lang="delphi">property ZoomFactor: double</source><br />
<br />
Read/Write property. Controls zooming of the RichMemo content. 1.0 - is no-zoom. Less than < 1.0 - decrease zoom, more than 1.0 - increasing zooming. If 0 is set - value defaults to 1.0 zoom factor (no zoom).<br />
<br />
=== HideSelection ===<br />
<br />
<source lang="delphi">property HideSelection: Boolean default false</source><br />
<br />
Read/Write property. If True RichMemo selection is hidden if the control is not focused.<br />
If False, the selection is shown all the time.<br />
===CanRedo===<br />
<br />
<source lang="delphi">property CanRedo: Boolean</source><br />
<br />
If True, there're actions in Undo queue that can be redone. <br />
If False, there are no actions in Undo queue that can be redone. Calling to Redo won't have any effect.<br />
<br />
===Transparent===<br />
[[Image:richmemo transparent.png|right|400px]]<br />
<source lang="delphi">property Transparent: Boolean</source><br />
<br />
If True, the background of richmemo is not filled with any color, leaving any underlying controls or windows to be visible. (Suggested to be used with TImage that would provide the background)<br />
If False, (default) the background is filled with a solid color <br />
<br />
Note that the property might not affect "border" style of the the richmemo. You need to set BorderStyle to bsNone in order to disable it.<br />
For macOS this also hides the focus ring (when the control is focused)<br />
<br />
* Supported by Win32, Qt5 and Cocoa<br />
<br />
=== Rtf ===<br />
<source lang="delphi">property Rtf: String</source><br />
[[Image:RtfEditor.PNG|right|400px]]<br />
<br />
Read/Write property that allows to read or write Rich-Text Formatted (RTF) text to the whole control. The intent of the property is to have an ability to set Rich-Text in design time, however the property would work in run-time as well. <br />
<br />
If then property value is an empty string then Lines property is used as a plain text to initialize control value in load-time.<br />
<br />
'''Note:''' The property is available in TRichMemo class only, it's not available (or even implemented in anyway) in TCustomRichMemo class. It is recommended to use LoadRichText/SaveRichText in order to modify the content of the page, just because they could consume less memory, than copying the whole rich-text into the memory.<br />
<br />
'''Cross-platform and Forward Compatibility Note:''' - currently the property is implemented based on the widgetset providede Rich-text read/write routines. It is very likely that in near future it would be changed to used RichMemo read/write RTF stream code only. The issue is with older systems might not support the latest version of RTF. <br />
<br />
So right now, you might run into an issue, If you create a project in Gtk2 and save the richmemo is design time containing Unicode characters. Then if you try to load it on the XP machine and would use widgetset native loader, you might see characters missing or rendered improperly. Changing to the usage of RichMemo RTF loader/saver will prevent this issue from happening. Currently you can also avoid it by registering RichMemo loaders (call RegisterRTFLoader, RegisterRTFSaver procedures in project initialization, before any RichMemo is loaded)<br />
<br />
==Events==<br />
===OnSelectionChange===<br />
<source lang="delphi">property OnSelectionChange: TNotifyEvent<br />
TNotifyEvent = procedure (Sender: TObject) of object;<br />
</source><br />
<br />
The event is fired whenever, selection is changed within RichMemo, either programmatically or due to the user action.<br />
<br />
== TRichEditForMemo ==<br />
The class helper that implements RichEdit programmatic interface.<br />
The helper is declared at RichEditHelpers unit, so you have to add it to the uses section.<br />
Helpers are available in FPC 2.6.0 or later. <br />
===Methods===<br />
====FindText====<br />
Searches a given range in the text for a target string<br />
<br />
===Properties===<br />
====SelAttributes====<br />
Reads/Modifies character attributes of the current selection.<br />
<br />
===Paragraph===<br />
Reads/Modifies paragraph attributes of the current selection.<br />
<br />
===RichMemoUtils===<br />
The unit is introduced to add some useful OS-specific features for handling working with RichMemo<br />
<br />
====InsertImageFromFile====<br />
<source lang="delphi">function InsertImageFromFile (const ARichMemo: TCustomRichMemo; APos: Integer;<br />
const FileNameUTF8: string;<br />
const AImgSize: TSize<br />
): Boolean = nil;<br />
</source><br />
''Disclaimer: the function would insert an image file into RichMemo (if implemented by the widgetset) But in a very inefficient way. The image would be read again and the memory would be re-allocated for the image every time. So please, don't use it for smileys in your chat instant messaging. A better API (with data caching) is considered. (That's why this method is not part of TCustomRichMemo class). ''<br />
* APos - position in the text<br />
* AImgSize - size to be inserted in POINTS, not pixels!. If both cx and cy are 0, the image would not be resized at all. If only one cx, cy is zero results are undefined. <br />
<br />
There's also an utility function '''InsertImageFromFileNoResize''' that calls InsertImageFromFile, passing size as 0.<br />
<br />
An example of use<br />
<source lang="delphi"><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
// running a dialog to select a picture to insert<br />
if OpenDialog1.Execute then <br />
// inserting the picture at the current RichMemo cursor position<br />
InsertImageFromFileNoResize(RichMemo1, RichMemo1.SelStart, OpenDialog1.FileName);<br />
end;<br />
</source><br />
<br />
===InsertStyledText,InsertColorStyledText,InsertFontText===<br />
The set of functions that simplify appending and replacing of styled text. The specified style of the text is applied on the insertion. The common rule for all functions, if InsPos (insertion position) is negative - the text is appended to the end. In order to insert the text in the beginning of the test InsPos should set to 0.<br />
<br />
The inserted text is not forced to be on a new line or add a new line. You might want to add LineFeed character to TestUTF8 parameter, if you want the inserted text to cause a new line to be started.<br />
<source lang="delphi">procedure InsertStyledText(<br />
const ARichMemo: TCustomRichMemo; <br />
const TextUTF8: String; <br />
AStyle: TFontStyles;<br />
InsPos : Integer = -1 )</source><br />
InsertStyledText inserts the styled text at the specified position. <br />
<br />
<source lang="delphi">procedure InsertColorStyledText(<br />
const ARichMemo: TCustomRichMemo; <br />
const TextUTF8: String; <br />
AColor: TColor; <br />
AStyle: TFontStyles;<br />
InsPos : Integer = -1 )</source><br />
InsertColorStyledText inserts the text with specified style and color at the specified position. to the end of the RichMemo<br />
<br />
<source lang="delphi">procedure InsertFontText(<br />
const ARichMemo: TCustomRichMemo; <br />
const TextUTF8: String; <br />
const prms: TFontParams; <br />
InsPos : Integer = -1 )</source><br />
InsertFontText inserts the text and applies specified FontParams to it.<br />
<br />
You might want to create a class helper to implement these functions as methods for RichMemo. Beware, if you're using [[#(Delphi) RichEdit like interface|Delphi-compatibility helper]] - you might right into a conflict.<br />
<br />
== Frequently Asked Questions ==<br />
=== Using RichMemo in Shared Libraries ===<br />
[http://bugs.freepascal.org/view.php?id=17412 Issue #17412] If you need to use the component in a shared library, you might need to add -fPIC key to the compiler option of "the package" and the "project".<br />
== (Delphi) RichEdit like interface ==<br />
[http://bugs.freepascal.org/view.php?id=14632 Issue #14632] A typical problem is porting an existing code that's using RichEdit from Delphi. RichMemo interface doesn't match RichEdit in many ways. But there're two ways to handle that:<br />
* you can either create a sub-class from TCustomRichMemo (or RichMemo) and implement Delphi RichEdit methods;<br />
* you can use RichMemoHelpers unit (fpc 2.6.0 or later required) and use methods provided by class Helpers that should; Currently SelAttributes and Paragraph properties are implemented.<br />
uses ... RichMemo, RichMemoHelpers;<br />
<br />
TForm = class<br />
RichMemo1 : TRichMemo;<br />
<br />
// SelAttributes property is not available in the base class<br />
// but added by a helper defined at RichMemoHelpers unit<br />
RichMemo1.SelAttributes.Name := 'Courier New';<br />
<br />
== Append mixed color text at the end of the RichMemo ==<br />
If you just need simple coloring then here is an example that will each time add a new line with random color (tested on Windows):<br />
<source lang="delphi"><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
var<br />
i, j: integer;<br />
str: string;<br />
begin<br />
with Richmemo1 do<br />
begin<br />
str := 'Newline text';<br />
i := Length(Lines.Text) - Lines.Count; // cr as #10#13 is counted only once so subtract it once<br />
j := Length(str) + 1; // +1 to make the cr the same format<br />
Lines.Add(str);<br />
SetRangeColor(i, j, Round(random * $FFFFFF));<br />
end;<br />
end;</source><br />
<span style="color:#0140FF">Newline text</span><br />
<span style="color:#507FAF">NewLine text</span><br />
<span style="color:#C04A90">NewLine text</span><br />
<br />
Alternative example for simple coloring (tested on Windows):<br />
<source lang="delphi"><br />
procedure TForm1.Button2Click(Sender: TObject);<br />
begin<br />
with RichMemo1 do<br />
begin<br />
Lines.Add('Line in red');<br />
SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clRed);<br />
<br />
Lines.Add('Line in blue');<br />
SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clBlue);<br />
<br />
Lines.Add('Normal line.');<br />
Lines.Add('Normal line.');<br />
<br />
Lines.Add('Line in green ');<br />
SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clGreen);<br />
end;<br />
end;</source> <br />
<br />
<span style="color:#FF0000">Line in red</span><br />
<span style="color:#0000FF">Line in blue</span><br />
<span style="color:#000000">Normal line.</span><br />
<span style="color:#000000">Normal line.</span><br />
<span style="color:#008000">Line in green</span><br />
<br />
If you need mixed coloring then this is an example that will add a new line with several different colored words (tested on Windows):<br />
<source lang="delphi"><br />
procedure TForm1.Button3Click(Sender: TObject);<br />
procedure AddColorStr(s: string; const col: TColor = clBlack; const NewLine: boolean = true);<br />
begin<br />
with RichMemo1 do<br />
begin<br />
if NewLine then<br />
begin<br />
Lines.Add('');<br />
Lines.Delete(Lines.Count - 1); // avoid double line spacing<br />
end;<br />
<br />
SelStart := Length(Text);<br />
SelText := s;<br />
SelLength := Length(s);<br />
SetRangeColor(SelStart, SelLength, col);<br />
<br />
// deselect inserted string and position cursor at the end of the text<br />
SelStart := Length(Text);<br />
SelText := '';<br />
end;<br />
end;<br />
begin<br />
AddColorStr('Black, ');<br />
AddColorStr('Green, ', clGReen, false);<br />
AddColorStr('Blue, ', clBlue, false);<br />
AddColorStr('Red', clRed, false);<br />
end;</source><br />
<br />
<span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span><br />
<span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span><br />
<span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span><br />
<br />
== Markup Language Parsing ==<br />
[[Image:mlparse.png|right|thumb|mlparse example]]<br />
The native rich-edit controls do not support a markup-language parsing.<br />
<br />
You'll need to write it yourself as shown in mlparse example<br />
<br />
==Internals==<br />
===Platform Support Matrix===<br />
Values in the matrix are not expected to be static (except for "carbon" column), each system is planned to support all features declared by RichMemo.<br />
<br />
Note that the table contains more features than published above. That means that the APIs are already available in RichMemo, but since they're unstable (on some platforms) it's considered "experimental" rather than promised.<br />
{| class="wikitable" width="100%"<br />
! Feature<br />
! Win32<br />
! Gtk2<br />
! Qt<br />
! Cocoa<br />
! Carbon<br />
|- <br />
| Font color and style selection<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
|-<br />
| Font background color<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
|-<br />
| Subscript,Superscript<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
| {{No}}<br />
| {{No}}<br />
|-<br />
| GetStyleRange<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
|-<br />
| Paragraph Alignment<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No|Almost Impossible}}<br />
|-<br />
| Paragraph Metrics<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
| {{Yes}}<br />
| {{No|Almost Impossible}}<br />
|-<br />
| Paragraph Tabs<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
| {{Yes}}<br />
| {{No}}<br />
|-<br />
|Zoom <br />
| {{Yes}}<br />
| {{No|Incomplete}}<br />
| {{No}}<br />
| {{Yes}}<br />
| {{No}}<br />
|-<br />
|Printing<br />
| {{Yes}}<br />
| {{No}}<br />
| {{No}}<br />
| {{No}}<br />
| {{No}}<br />
|-<br />
| RTF Load/Save<br />
| {{Yes|OS}}<br />
| {{Yes|RichMemo}}<br />
| {{No}}<br />
| {{Yes|OS}}<br />
| {{Yes|OS}}<br />
|-<br />
|Insertables<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
| {{No}}<br />
| {{No}}<br />
|-<br />
|Transparent<br />
| {{Yes}}<br />
| {{No}}<br />
| {{Yes}}<br />
| {{Yes}}<br />
| {{No}}<br />
|}<br />
<br />
===Win32===<br />
[[Image:rm_win32_notheme.PNG|thumb|RichMemo without Theme patch]]<br />
[[Image:rm_win32_theme.PNG|thumb|RichMemo with Theme]]<br />
* [https://msdn.microsoft.com/en-us/library/windows/desktop/bb787605%28v=vs.85%29.aspx RichEdit] is used as system widgetset. The latest known .dll is loaded and initialized on start. Please note that RichEdit 1.0 would not support most of the features. <br />
* The internal wrapper of RichEditManager is provided in order to be compatible with Win 9x. However, it was never tried or tested. It's also expected that TOM object could be wrapped as one of the RichEditManager implementation. However, having the "manager" in place could be removed completely.<br />
* According to the internet, RichEdit control was not updated by Microsoft to support Theme drawing (since XP and up to Windows 8.1). Thus, RichEdit might always look like an old Win9x 3d-framed control. Based on the patch provided at the [http://bugs.freepascal.org/view.php?id=25111|reported issue] a way to override [https://msdn.microsoft.com/en-us/library/windows/desktop/dd145212%28v=vs.85%29.aspx WM_NCPAINT] method was introduced in r4153.<br />
<br />
Win32RichMemo provides a global variable NCPaint. It's a handler of non-client area drawing. By default, it attempts to draw themed border (see Win32RichMemo.ThemedNCPaint for implementation). It provides good results on Windows XP themes, but later themes (where animations are used), the results are not so great and should be updated.<br />
<br />
In order to let system do NCPaint only (i.e. LCL implementation is causing issues or new windows updated RichEdit to draw borders properly), you can change NCPaint value at runtime, resetting it to nil<br />
uses<br />
RichMemo, ..{$ifdef WINDOWS}Win32RichMemo{$endif}<br />
<br />
initialization<br />
{$ifdef WINDOWS}<br />
Win32RichMemo.NCPaint:=nil;<br />
{$endif}<br />
<br />
You can also provide your own implementation of NCPaint. However, if you implement proper animated theme drawing, please provide patch.<br />
<br />
The behavior is Windows implementation specific and should not (and will not be) part of RichMemo interface.<br />
<br />
There's additional information about Windows RICHEDIT that can be found at:<br />
* MSDN blog by "Murray Sargent: Math in Office" (https://docs.microsoft.com/en-us/archive/blogs/murrays/).<br />
<br />
===Gtk2===<br />
* [https://developer.gnome.org/gtk2/stable/GtkTextView.html GtkTextView] is used as system widgetset<br />
* Sub and Superscript are not supported natively, an extra code is implemented in gtk2richmemo in order to implement them.<br />
* Bulletin and numbered lists are emulated.<br />
====2.1.0 (trunk/later)====<br />
Gtk2 internal functions got changes. Specifically function GetWidgetInfo was modified, making RichMemo to fail on compilation.<br />
<br />
in order to adapt the change '''RMLCLTRUNK''' must be defined. It can be easily specified using '''richmemopackage''' compiler options.<br />
<br />
[[Image:rmlcltrunkdefine.png]]<br />
<br />
===Gtk3===<br />
The APIs between Gtk2 and [https://developer.gnome.org/gtk3/stable/GtkTextView.html Gtk3] are not really different. The major difference is in painting. For Gtk3 Cairo canvas is heavily used. The only area it affects is "Internals". IF any one wants to contribute Gtk2 implementation - please create separate gtk3richmemoXXX units. No {$ifdefs} should be created in gtk2 units.<br />
<br />
===Cocoa===<br />
* [https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTextView_Class/index.html NSTextView] is used for Cocoa as system widgetset<br />
* Not every font-family provides ''italic'' font. Thus even if you pass fsItalic as part of Style for a font, it might be ignored.<br />
====Lazarus 1.8 and earlier====<br />
You need to add a define RMLCL18 for the package compilation.<br />
<br />
===Qt===<br />
* [http://doc.qt.io/qt-4.8/qtextedit.html QTextEdit] widget is used. Current C-mappings for Qt are missing a lot of RichText APIs, thus full implementation is currently impossible. <br />
* The following classes need to be mapped to C-functions:<br />
** [http://doc.qt.io/qt-4.8/qtextblockformat.html QTextBlockFormatH] needed for paragraphs alignments (such as paragraph indents, line spacing and tabs). QTextEdit only exposes paragraph alignment.<br />
** [http://doc.qt.io/qt-4.8/qtextcharformat.html QTextCharFormatH] needed for additional character formatting (i.e. vertical alignment, links support). QTextEdit only exposes font styles.<br />
<br />
==See also==<br />
* [[RichMemo/Features]] - Comments on some features in progress.<br />
* [[RichMemo/WorkArounds]] - Some notes about features not yet complete.<br />
* [[RichMemo/Defines]] - Additional package switches that allow to resolve compiling/runtime issues.<br />
* [[RichMemo/Samples]] - More examples.<br />
* [[RichMemo/FAQ]] - Frequently (Forum) Asked Questions<br />
* [https://sites.google.com/site/mynotex/ MyNotex] - Application which uses modified by Massimo Nardello RichMemo package on Linux-Gtk2.<br />
* [[TMemo]]<br />
* [[KControls#KMemo_control|KMemo control]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Developer_pages&diff=153519Developer pages2022-08-16T16:18:11Z<p>Skalogryz: </p>
<hr />
<div>Animations of commit history made with gource by Bernd K. :<br />
<br />
11 years of Lazarus development: http://vimeo.com/37066125<br />
<br />
Eight months in 2011-2012: http://vimeo.com/36868024<br />
<br />
== Lazarus Developer Team ==<br />
<br />
{| class="wikitable"<br />
! Member !! Photo !! Role in Lazarus !! IRC nick !! GIT nick !! GMT<br />
|----<br />
|[[user:AndrewH|Andrew Haines]]||(no photo)||Gtk2, LHelp||drewski||?||GMT-05<br />
|---<br />
|[[user:Ask|Alexander Klenin]]||(no photo)||TAchart||_Ask_||?||GMT+10<br />
|----<br />
|Balázs Székely||(no photo)||||||balazs_szekely||?<br />
|----<br />
|[[user:Bart|Bart Broersma]]||(no photo)||LCL, Win32||||flyingsheep||GMT+01<br />
|----<br />
|[[user:Kirkpatc|Chris Kirkpatrick]]||(no photo)||not active currently, was documentation||?||?||GMT+00<br />
|----<br />
|[[user:Dblaszijk|Darius Blaszijk]]||(no photo)||not active currently, was IDE||daar||?||GMT+01<br />
|----<br />
|[[User:Skalogryz|Dmitry Boyarintsev]]||(no photo)||cocoa, carbon, LCL||skalogryz||?||?<br />
|----<br />
|Don Siders||(no photo)||Documentation||?||dsiders||?<br />
|----<br />
|[[user:Sekelsenmat|Felipe Monteiro de Carvalho]]||(no photo)||LCL, customdrawn widgetset||sekelsenmat||?||GMT+01<br />
|----<br />
|[[user:Jesusrmx|Jesus Reyes]]||(no photo)||database controls||jesusra||jramx||GMT-06<br />
|----<br />
|[[user:JuhaManninen|Juha Manninen]]||[[Image:JuhaManninen.jpg|100px]]||IDE, Delphi Converter||JuhaManninen||JuhaManninen||GMT+02<br />
|----<br />
|[[user:Wile64|Laurent Jacques]]||(no photo)||not active currently, was images||?||?||GMT+01<br />
|----<br />
|[[User:Luizmed|Luiz Américo Pereira Câmara]]||[[Image:luiz_americo.png|100px]]||LCL,win32,gtk2||blikblum||?||GMT-03<br />
|----<br />
|[[user:Paul_Ishenin|Paul Ishenin]]||[[Image:paul_ishenin.jpg|100px]]||LCL, win32, IDE||Paul_Ishenin||?||GMT+07<br />
|----<br />
|[[user:Marc|Marc Weustink]]||(no photo)||LCL, debugger||giantm||mweustink||GMT+01<br />
|----<br />
|[[user:Martin|Martin Friebe]]||[[Image:MartinFriebe.jpg|100px]]||synedit + IDE source editor, debugger||martin_frb||martin_frb||GMT+00<br />
|----<br />
|[[user:Mattias2|Mattias Gärtner]]||(no photo)||codetools, LCL, IDE||lazidealist||lazidealist||GMT+01<br />
|----<br />
|[[user:Maxim|Maxim Ganetsky]]||(no photo)||i18n, l10n, IDE, LCL||KritiK||ganmax||GMT+04<br />
|----<br />
|[[user:Mazen|Mazen NEIFER]]||(no photo)||linux packages||?||mazen.neifer||??<br />
|----<br />
|[[user:Michael|Michael Hess]]||(no photo)||not active currently, lazarus founder||?||?||??<br />
|----<br />
|[[user:Michl|Michael W. Vogel]]||[[Image:MichaelVogel.png|100px]]||LCL, Win32||?||michl1||GMT+01<br />
|----<br />
|[[user:Neli|Micha Nelissen]]||(no photo)||not active currently, was win32||neli||?||GMT+01<br />
|----<br />
|[[user:Ondrej|Ondrej Pokorny]]||(no photo)||not active currently||?||onpok||GMT+01<br />
|----<br />
|[[user:Tombo|Tom Gregorovic]]||(no photo)||not active currently, was carbon||Tombo||?||GMT+01<br />
|----<br />
|[[user:Vincent|Vincent Snijders]]||[[Image:Vincent_Snijders.jpg|100px]]||wiki server maintainer, previously active for win32, as release manager, fixes branch supporter||fpcfan||?||GMT+01<br />
|----<br />
|[[user:wp|Werner Pamler]]||(no photo)|| TAChart, various components || ? ||wpam|| GMT+01<br />
|----<br />
|[[user:Zeljan|Zeljan Rikalo]]||[[Image:Zeljan.jpg|100px]]||qt, gtk2, LCL||zeljan||zeljan1||GMT+01<br />
|}<br />
<br />
* See also [https://gitlab.com/freepascal.org/lazarus/lazarus/-/project_members Project Members on GitLab]<br />
<br />
== FPC Developer Team ==<br />
{{stub|reason=[[#FPC Developer Team|empty section]] [[User:Kai Burghardt|Kai Burghardt]] ([[User talk:Kai Burghardt|talk]]) 17:38, 30 April 2020 (CEST)}}<br />
<br />
* see also [https://freepascal.org/aboutus.html Free Pascal development team]<br />
* see also [https://gitlab.com/groups/freepascal.org/fpc/-/group_members FPC group members on GitLab]<br />
<br />
[[Category:Lazarus internals]]<br />
[[Category:SOPs]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Developer_pages&diff=153518Developer pages2022-08-16T16:17:57Z<p>Skalogryz: </p>
<hr />
<div>Animations of commit history made with gource by Bernd K. :<br />
<br />
11 years of Lazarus development: http://vimeo.com/37066125<br />
<br />
Eight months in 2011-2012: http://vimeo.com/36868024<br />
<br />
== Lazarus Developer Team ==<br />
<br />
{| class="wikitable"<br />
! Member !! Photo !! Role in Lazarus !! IRC nick !! GIT nick !! GMT<br />
|----<br />
|[[user:AndrewH|Andrew Haines]]||(no photo)||Gtk2, LHelp||drewski||?||GMT-05<br />
|---<br />
|[[user:Ask|Alexander Klenin]]||(no photo)||TAchart||_Ask_||?||GMT+10<br />
|----<br />
|Balázs Székely||(no photo)||||||balazs_szekely||?<br />
|----<br />
|[[user:Bart|Bart Broersma]]||(no photo)||LCL, Win32||||flyingsheep||GMT+01<br />
|----<br />
|[[user:Kirkpatc|Chris Kirkpatrick]]||(no photo)||not active currently, was documentation||?||?||GMT+00<br />
|----<br />
|[[user:Dblaszijk|Darius Blaszijk]]||(no photo)||not active currently, was IDE||daar||?||GMT+01<br />
|----<br />
|[[User:Skalogryz|Dmitry Boyarintsev]]||(no photo)||cocoa, carbon, LCL||skalogryz||?||?<br />
|----<br />
|Don Siders||(no photo)||Documentation||?||dsiders||?<br />
|----<br />
|[[user:Sekelsenmat|Felipe Monteiro de Carvalho]]||(no photo)||LCL, customdrawn widgetset||sekelsenmat||?||GMT+01<br />
|----<br />
|[[user:Jesusrmx|Jesus Reyes]]||(no photo)||database controls||jesusra||jramx||GMT-06<br />
|----<br />
|[[user:JuhaManninen|Juha Manninen]]||[[Image:JuhaManninen.jpg|100px]]||IDE, Delphi Converter||JuhaManninen||JuhaManninen||GMT+02<br />
|----<br />
|[[user:Wile64|Laurent Jacques]]||(no photo)||not active currently, was images||?||?||GMT+01<br />
|----<br />
|[[User:Luizmed|Luiz Américo Pereira Câmara]]||[[Image:luiz_americo.png|100px]]||LCL,win32,gtk2||blikblum||?||GMT-03<br />
|----<br />
|[[user:Paul_Ishenin|Paul Ishenin]]||[[Image:paul_ishenin.jpg|100px]]||LCL, win32, IDE||Paul_Ishenin||?||GMT+07<br />
|----<br />
|[[user:Marc|Marc Weustink]]||(no photo)||LCL, debugger||giantm||mweustink||GMT+01<br />
|----<br />
|[[user:Martin|Martin Friebe]]||[[Image:MartinFriebe.jpg|100px]]||synedit + IDE source editor, debugger||martin_frb||martin_frb||GMT+00<br />
|----<br />
|[[user:Mattias2|Mattias Gärtner]]||(no photo)||codetools, LCL, IDE||lazidealist||lazidealist||GMT+01<br />
|----<br />
|[[user:Maxim|Maxim Ganetsky]]||(no photo)||i18n, l10n, IDE, LCL||KritiK||ganmax||GMT+04<br />
|----<br />
|[[user:Mazen|Mazen NEIFER]]||(no photo)||linux packages||?||mazen.neifer||??<br />
|----<br />
|[[user:Michael|Michael Hess]]||(no photo)||not active currently, lazarus founder||?||?||??<br />
|----<br />
|[[user:Michl|Michael W. Vogel]]||[[Image:MichaelVogel.png|100px]]||LCL, Win32||?||michl1||GMT+01<br />
|----<br />
|[[user:Neli|Micha Nelissen]]||(no photo)||not active currently, was win32||neli||?||GMT+01<br />
|----<br />
|[[user:Ondrej|Ondrej Pokorny]]||(no photo)||not active currently||?||onpok||GMT+01<br />
|----<br />
|[[user:Tombo|Tom Gregorovic]]||(no photo)||not active currently, was carbon||Tombo||?||GMT+01<br />
|----<br />
|[[user:Vincent|Vincent Snijders]]||[[Image:Vincent_Snijders.jpg|100px]]||wiki server maintainer, previously active for win32, as release manager, fixes branch supporter||fpcfan||?||GMT+01<br />
|----<br />
|[[user:wp|Werner Pamler]]||(no photo)|| TAChart, various components || ? ||wpam|| GMT+01<br />
|----<br />
|[[user:Zeljan|Zeljan Rikalo]]||[[Image:Zeljan.jpg|100px]]||qt, gtk2, LCL||zeljan||zeljan1||GMT+01<br />
|}<br />
<br />
* See also [https://gitlab.com/freepascal.org/lazarus/lazarus/-/project_members Project Members on GitLab]<br />
<br />
== FPC Developer Team ==<br />
{{stub|reason=[[#FPC Developer Team|empty section]] [[User:Kai Burghardt|Kai Burghardt]] ([[User talk:Kai Burghardt|talk]]) 17:38, 30 April 2020 (CEST)}}<br />
<br />
* see also [https://freepascal.org/aboutus.html Free Pascal development team]<br />
* see also [https://gitlab.com/groups/freepascal.org/fpc/-/group_members FPC group members on GitLab]<br />
<br />
[[Category:Lazarus internals]]<br />
[[Category:SOPs]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_Team_-_Git_Workflows&diff=153517Lazarus Team - Git Workflows2022-08-16T16:17:35Z<p>Skalogryz: /* Continuous integration (CI) System */</p>
<hr />
<div>This page contains workflows for Lazarus team members, when working with GIT.<br />
<br />
= git pull --rebase =<br />
<br />
;Commits made by team members should have a linear history:<br />
<br />
If you have local commits to push, and the remote branch has new commits too, then use '''rebase'''.<br />
Do not create any merge commits.<br />
<br />
git pull --rebase<br />
or<br />
git rebase<br />
<br />
= Merge Requests / Pull Requests =<br />
<br />
Merge requests should be included by creating a merge commit<br />
<br />
git merge merge-request/foo<br />
<br />
{{Note| To pull merger request to your local git: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#checkout-locally-by-modifying-gitconfig-for-a-given-repository }}<br />
<br />
= Cherry picking / Merging =<br />
<br />
Do not "squash" commits when cherry picking.<br />
<br />
If commits are squashed, then [https://git-scm.com/docs/git-cherry "git cherry"] wont work.<br />
<br />
Use<br />
git cherry pick -x<br />
The "-x" adds "cherry picked from commit"<br />
<br />
= ".gitattributes" - Adding new file extensions =<br />
<br />
{{Warning| Adding new extensions can have unwanted side effects on existing file. }}<br />
<br />
If you add files with an new extension (not yet covered by .gitattributes) then it is best to add that extension (or the concrete file)<br />
<br />
Files not covered by .gitattributes will be left to git figuring out on its own, if they are text. <br />
Since this affects line endings, it is best to tell git, what we want the file to be.<br />
<br />
<b><u>After editing .gitattributes you must run (before committing): </u></b><br />
git add --renormalize .<br />
<br />
Then check '''git status''' which files were affected.<br />
<br />
= Issue Tracker =<br />
<br />
== Milestone ==<br />
<br />
When closing an issue, ensure to set the correct milestone.<br />
<br />
If the issues is (or will be) merged to a fixes branch, set the milestone for the fixes release.<br />
(If the merge later fails, the milestone must be amended).<br />
<br />
If you do not merge (cherry pick) yourself:<br />
* leave the issue open<br />
* Set the fixes milestone<br />
* add the label<br />
:: merge::merge-and-close<br />
<br />
= Continuous integration (CI) System =<br />
<br />
On every commit continuous integration (CI) system creates a pipeline, which builds Lazarus for several platforms/widgetsets. If the build fails, it notifies the creator of commit which caused the failure.<br />
<br />
The building process happens in a virtual machine (Docker container), which loads an operating system image. Our image is based on Debian 11 x86_64 and at the time of writing this section contains:<br />
<br />
* GTK2, GTK3, Qt5 libraries;<br />
* FPC (versions 3.2.0 [oldstable], 3.2.2 [stable]) for the following targets:<br />
** x86_64-linux;<br />
** x86_64-win64 (cross-compiler);<br />
** i386-win32 (cross-compiler).<br />
<br />
Docker image configuration is defined in ''Dockerfile.buildenv'' file, and pipeline configuration is defined in ''.gitlab-ci.yml'' (both files are located in Lazarus root directory).<br />
<br />
At the time of writing this section our pipeline consists of three stages:<br />
<br />
# Update our Docker image (see above). This process is triggered manually and should be only run when ''Dockerfile.buildenv'' and/or FPC version constants in ''.gitlab-ci.yml'' file have been changed.<br />
# Update Qt5Pas bindings and cache this job's result. This process is triggered automatically when ''lcl/interfaces/qt5/cbindings'' directory contents are changed. Its results are required for subsequent building of Lazarus with Qt5 widgetset. This is needed because building Qt5 bindings takes a long time (~20 min) and they are rarely changed.<br />
# Build Lazarus for various widgetsets/platforms. At the time of writing this section the following jobs for building Lazarus are defined:<br />
#* x86_64-gtk2 bigide (oldstable and stable FPC);<br />
#* x86_64-qt5 bigide (oldstable and stable FPC);<br />
#* x86_64-win64 bigide (oldstable and stable FPC);<br />
#* i386-win32 bigide (oldstable and stable FPC).<br />
<br />
At the time of writing this section the following jobs for building Lazarus are built automatically on every commit:<br />
* x86_64-gtk2 bigide (stable FPC);<br />
* x86_64-qt5 bigide (stable FPC);<br />
* i386-win32 bigide (oldstable FPC).<br />
All others can be triggered manually via GitLab web interface.<br />
<br />
In GitLab web interface you can notice '''CI/CD''' menu item at the left. By clicking '''CI/CD'''->'''Pipelines''' you will get to pipelines list, where you can see build status for each commit. By clicking circles in the '''Stages''' column you will get to submenus which will show status of all jobs and will allow to run/rerun them manually (by clicking at '''play'''/'''retry''' button) and to look at job's log (by clicking at job's name).<br />
<br />
Normally you will see only two stages (1 and 3). Stage 2 is only shown when GitLab thinks that Qt5Pas bindings have to be rebuilt (''lcl/interfaces/qt5/cbindings'' directory contents are changed).<br />
<br />
Important notice for Qt5 jobs:<br />
<br />
They use cached results from stage 2. In absolute majority of the builds this should not cause any problems. But still if you get Qt5 widgetset build failure related to Qt5Pas version mismatch or missing cache, make sure to go to the '''Pipelines''' page and do the following:<br />
# Stop the currently running pipeline if needed.<br />
# Press '''Clear runner caches''' button.<br />
# Press '''Run pipeline''' button (you will be asked about defining variables, just skip this step (we don't use them) and confirm running pipeline).<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Revision control]]<br />
[[Category:SOPs]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=WebPageDevelopment&diff=153516WebPageDevelopment2022-08-16T16:17:22Z<p>Skalogryz: </p>
<hr />
<div>The main web page of Lazarus project can be found here :<br />
http://www.lazarus-ide.org/<br />
<br />
It is done with HTML and PHP, and connects to SMF (Simple Machines) forum SW.<br />
<br />
The sources are in a subversion repository :<br />
https://svn.freepascal.org/svn/lazaruswebsite/<br />
<br />
which is almost the same address that Lazarus sources have, only "lazarus" is replaced with "lazaruswebsite".<br />
It is readable by anyone.<br />
<br />
Testing the page locally requires normal web server + PHP installation.<br />
SMF (Simple Machines) does not need to be installed though.<br />
The PHP code is clever enough to insert "dummy" forum data when real<br />
data is not available.<br />
<br />
The page can be improved by anyone by sending patches through bug tracker, the same way that patches are sent for pascal sources.<br />
However, if the changes are clearly a matter of taste (like colors) then please discuss the changes first in mailing list or forum.<br />
<br />
When a patch is applied to SVN, it is updated automatically to a test server :<br />
http://test.lazarus-ide.org/<br />
After it is checked for visual appearance and for potential security issues, it will be updated manually to the "production" server.<br />
<br />
However the test server is not very useful for a contributor because the changes must be tested locally anyway.<br />
<br />
[[Category:Lazarus internals]]<br />
[[Category:SOPs]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_release_engineering&diff=153515Lazarus release engineering2022-08-16T16:17:05Z<p>Skalogryz: /* Related pages */</p>
<hr />
<div><br />
{{Note|This page is still work in progress. For feedback please use our mailing list.}}<br />
<br />
== Checklist ==<br />
<br />
<table width="100%"><tr valign="top"><br />
<td style="min-width:40%"><br />
<br />
=== Major Release ===<br />
<br />
* Decide on new release<br />
** Check internally with the team <br />
** Decide on approx Date for first RC (date may change). This may be optional.<br />
* Prepare<br />
** Create fixes branch in SVN<br />
** Create fixes page on wiki<br />
* Announce<br />
** <u>Special announcement</u> for translators.<br />
** <u>Public announcement</u> with approx RC date (if available).<br />
<br />
=== For each Release Candidate ===<br />
* Between 1 and 2 weeks before RC is due<br />
** Check internally with the team <br />
** <u>Public reminder/announcement</u> that RC is due.<br />
** If needed: Reschedule/Delay<br />
* Publish the Release Candidate<br />
** Tag RC in SVN<br />
** Build and upload<br />
** <u>Public announcement</u> of RC<br />
<br />
Typical life time of an RC can be expected to be between 3 and 6 weeks. But in the end will be decided at the discretion of the team.<br />
<br />
=== For the Release ===<br />
* At least 2 weeks before Release<br />
** Check internally with the team <br />
** Public reminder/announcement that Release is due. No more RC planned.<br />
** Repeat <u>Special announcement</u> for translators.<br />
* 1 week before Release - Depending on feedback (the team should have 3 or 4 days time to discuss this):<br />
*** keep decision to release<br />
*** move date of release<br />
*** schedule another RC<br />
** 3 to 5 days later: <u>Public announcement</u> of above decision, if a change to the plan was made. <br />
* Make the Release<br />
** Tag RC in SVN<br />
** Build and upload<br />
** <u>Public announcement</u> of Release<br />
<br />
<br />
<br />
</td><br />
<td style="min-width:40%"><br />
=== Minor Release ===<br />
<br />
* Decide on new Release (Day: approx -4 / Duration 3-4 days)<br />
** Check internally with the team (3 to 4 days)<br />
** Release Date will be 2 weeks from announcement (1 week for community feedback)<br />
* Prepare<br />
** Update fixes page on wiki (New section "Fixes for ..." next release<br />
<br />
* Announce upcoming Release (Day: 0 / Duration 7-8 days)<br />
** <u>Special announcement</u> for translators.<br />
** <u>Public announcement</u> with approx date.<br />
*** Mail<br />
*** Forum<br />
: Ask for <br />
:*new regressions<br />
:*missing merges<br />
<br />
Community has about 1 week to respond.<br />
Urgent matters can still be reported during "approve" phase.<br />
<br />
Depending on feedback received: reschedule and if needed re-announce.<br />
<br />
==== Approve Release (Start at day: 7 to 9 / Duration 3/4 days) ====<br />
<br />
* Check internally with the team (3 to 4 days)<br />
<br />
Depending on feedback received: reschedule and if needed re-announce.<br />
<br />
==== Prepare Release (Start at day: 10 to 12 / Duration 3/4 days) ====<br />
<br />
* TAG SVN<br />
* Build docs => update binaries svn<br />
* Start building<br />
* Build Release binaries<br />
** Test<br />
* Upload (staged)<br />
<br />
At the same time:<br />
* Update mantis<br />
* Collect checksums and commit to web-page svn<br />
** Optional: Update webpage to contain checksums<br />
* Update web-page svn with new download links (to be published *after* release)<br />
<br />
==== Release (At day 14 to 16) ====<br />
<br />
* Make files public on Sourceforge (un-stage)<br />
** Set files as current release<br />
<br />
* <u>Public announcement</u> of Release<br />
** Mail<br />
** Forum<br />
** Twitter<br />
** Sourceforge news<br />
<br />
* Update web-page (Download-links / Checksums)<br />
<br />
<br />
</td><br />
</tr></table><br />
<br />
== Merging fixes ==<br />
<br />
Team members will add any revision that should be merged to the "fixes branch" page (see "Related Pages").<br />
<br />
User can contact Team members<br />
* on the forum<br />
* on the mail list<br />
* on open & assigned mantis reports<br />
to request revisions to be added.<br />
<br />
== Other Items ==<br />
<br />
In addition to the items on the checklist:<br />
* create new targets on Mantis, with each RC, and for the Release<br />
* Publish checksums on webpage<br />
* Update links on webpage<br />
* Updating of help files<br />
* Announcements for Translators<br />
<br />
TODO:<br />
Decide on channels for public feedback on:<br />
* Open regressions<br />
* Missing merges<br />
<br />
== Checklist (Suggestions) ==<br />
<br />
Please add suggestions here. <br />
The editing of the Checklist above should be reserved to the team. Thanks.<br />
<br />
== How to merge ==<br />
<br />
The lazarus developers have decided to use the native svn merge for this branch. Other branches used the svnmerge.py script to manage the revisions to be merged.<br />
<br />
* TODO: Maybe this information should be put in a separate page.<br />
<br />
[[SVN_Migration#Merge_with_plain_svn]]<br />
<br />
=== Using TortoiseSVN ===<br />
As noted in the link above using TortoiseSVN is more or less self explaining. <br />
<br />
* TODO: add some screen shots.<br />
<br />
When committing, in the recent messages a nice commit message is available.<br />
<br />
Tested with TortoiseSVN 1.7.6, SVN 1.7.4.<br />
<br />
=== Show list of revision that can be merged ===<br />
<br />
In the lazarus fixes_1_0 directory do:<br />
svn mergeinfo ^/trunk --show-revs eligible<br />
Or if you don't have a fixes directory checked out, you can pass the URL path of the fixes branch:<br />
svn mergeinfo ^/trunk http://svn.freepascal.org/svn/lazarus/branches/fixes_1_0 --show-revs eligible<br />
<br />
=== Show list of revision that have been merged ===<br />
<br />
In the lazarus fixes_1_0 directory do:<br />
svn mergeinfo ^/trunk --show-revs merged<br />
<br />
=== Merging revisions from trunk ===<br />
<br />
To merge one or more revisions from trunk, use the svn merge command. For example to merge revision 36506 and 36510 use:<br />
svn merge -c 36506,36510 ^/trunk<br />
<br />
To generate a commit log message, use:<br />
svn log ^/trunk -c 36506,36510 > svnmerge-commit-message.txt<br />
<br />
Edit svnmerge-commit-message.txt with your favorite text editor and add a first line like:<br />
Merged revision 36506,36510 from /trunk.<br />
<br />
Commit this with:<br />
svn commit -F svnmerge-commit-message.txt<br />
<br />
=== Blocking a revision to be merged ===<br />
<br />
Sometimes you want to block a revision to be merged, i.e. you want to make sure that this revision is never merged to the fixes branch, for example, because it contains a new feature or it contains a version number change not applicable to the fixes branch, but only to trunk. To block revision 36507, use:<br />
svn merge -c 36507 --record-only ^/trunk<br />
Then you can create a log message with:<br />
svn log ^/trunk -c 36507 > svnmerge-commit-message.txt<br />
Edit svnmerge-commit-message.txt with your favorite text editor and add first line like:<br />
Blocked revision 36507 from /trunk<br />
Then commit this change:<br />
svn commit -F svnmerge-commit-message.txt<br />
<br />
== Previous releases ==<br />
<br />
* [[Lazarus 0.9.30 todo]]<br />
* [[Lazarus 0.9.28 todo]]<br />
* [[Detailed Lazarus 0.9.22 todo|Lazarus 0.9.22 todo]]<br />
<br />
* [[Lazarus 0.9.30.2 release plan]]<br />
* [[Lazarus 0.9.28.2 release plan]]<br />
<br />
== See also ==<br />
<br />
* [[Detailed Lazarus release template todo]]<br />
<br />
== Related pages ==<br />
<br />
{{Navbar Lazarus Release Notes}}<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Development]]<br />
[[Category:Release engineering]]<br />
[[Category:SOPs]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_Continuous_Integration&diff=153514Lazarus Continuous Integration2022-08-16T16:16:51Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>The page describes GitLab continous integration setup for Lazarus project<br />
<br />
==Continuous Integration==<br />
On every commit CI system creates a pipeline, which builds Lazarus for<br />
several platforms/widgetsets. If the build fails, it notifies the<br />
creator of commit which caused the failure.<br />
<br />
The building process happens in a virtual machine (Docker container),<br />
which loads an operating system image. Our image is based on Debian 11<br />
x86_64 and currently contains GTK2, GTK3, Qt5 libraries and FPC<br />
(versions 3.2.0, 3.2.2) for x86_64-linux with x86_64-win64 and<br />
i386-win32 cross-compilers.<br />
<br />
Docker image configuration is defined in Dockerfile.buildenv file, and<br />
pipeline configuration is defined in .gitlab-ci.yml (both files located<br />
in Lazarus root directory).<br />
<br />
===Pipe Line===<br />
Our pipeline currently consists of three stages:<br />
<br />
1. Update our Docker image (see above). This process is triggered<br />
manually and should be only run when Dockerfile.buildenv and/or FPC<br />
version constants in .gitlab-ci.yml file were updated. Don't do this<br />
without consulting with me first.<br />
<br />
2. Update Qt5Pas bindings and cache the result of this job. This process<br />
is triggered automatically when `lcl/interfaces/qt5/cbindings` directory<br />
contents are changed. Its results are required for subsequent building<br />
of Lazarus with Qt5 widgetset. This is needed because building Qt5<br />
bindings takes a long time (~20 min) and they are rarely changed.<br />
<br />
3. Build Lazarus for various widgetsets/platforms. Jobs currently<br />
defined are building Lazarus for x86_64-gtk2, x86_64-qt5, x86_64-win64,<br />
i386-win32 with two FPC versions (currently FPC 3.2.0 [oldstable] and<br />
3.2.2 [stable]). x86_64-gtk2 (FPC 3.2.2), x86_64-qt5 (FPC 3.2.2) and<br />
i386-win32 (FPC 3.2.0) are built automatically on every commit. All<br />
others can be triggered manually via Gitlab web interface.<br />
<br />
In [https://gitlab.com/freepascal.org/lazarus/lazarus Gitlab web interface] you can notice `CI/CD` menu item at the left. By<br />
clicking `CI/CD`->`Pipelines` you will get to pipelines list, where you<br />
can see build status for each commit. By clicking circles in the<br />
`Stages` column you will get to submenus which will show status of all<br />
jobs and will allow to run/rerun them manually (by clicking at<br />
`play`/`retry` button) and to look at job's log (by clicking at job's name).<br />
<br />
Normally you will see only two stages (1 and 3). Stage 2 is only shown<br />
when Gitlab thinks that Qt5Pas bindings have to be rebuilt<br />
(`lcl/interfaces/qt5/cbindings` directory contents are changed).<br />
<br />
'''Important notice for Qt widgetsets:'''<br />
<br />
They use cached results from stage 2. In absolute majority of the builds<br />
this should not cause any problems. But still if you get Qt widgetset<br />
build failure related to Qt5Pas version mismatch or missing cache, make<br />
sure to go to the `Pipelines` page, stop the currently running pipeline<br />
if needed, then press `Clear runner caches` button, then press `Run<br />
pipeline` button (you will be asked about defining variables, just skip<br />
this step (we don't use them) and confirm running pipeline).<br />
<br />
==See Also==<br />
[[Category:CI/CD]]<br />
[[Category:SOPs]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Cross_Compile_to_RasPi_from_Linux&diff=153513Cross Compile to RasPi from Linux2022-08-16T16:09:56Z<p>Skalogryz: /* See Also */</p>
<hr />
<div><br />
<br />
=== Introduction ===<br />
<br />
This page is about setting up a Cross Compiler from a Linux Box to a [[Lazarus_on_Raspberry_Pi | Raspberry Pi]]. You should be able to select arm as the CPU in Lazarus or if just using FPC, choose to use the ppcrossarm compiler and produce v6 arm binaries. Raspberry Pi is just a specific example of arm, only small changes would seem necessary on other similar platforms.<br />
<br />
* Here we talk about making armhf software, not armel, there is a very brief discussion about that lower down.<br />
* Tested on an Ubuntu 21.04 and Debian Bullseye System. Its should be similar on most mainstream Linux platforms.<br />
* This recipe assumes '''you already have a current FPC''' and, possibly Lazarus installed. You MUST use packages downloaded from the FPC/Lazarus SourceForge site, its most likely that FPC and Lazarus packages from your Distribution will not work as expected. <br />
* A large part of this install is done as root. Working like that gives you the potential to make a real mess of your system, please be very careful.<br />
<br />
'''This page is under development and the recipe presented is still being tested.'''<br />
<br />
=== Getting the necessary libraries ===<br />
<br />
Firstly, we are going to need some reasonably current libraries from your Raspberry Pi, as near as I can determine this is the best way to get the necessary libraries. <br />
<br />
On the pi, we will tar up what we need, this does not need to be done as root.<br />
<br />
tar czf usr_lib_arm-linux-gnueabihf.tgz /usr/lib/arm-linux-gnueabihf<br />
tar czf lib_arm-linux-gnueabihf.tgz /lib/arm-linux-gnueabihf<br />
<br />
Next, we need the crt files, first, find where they are, yours may be in a directory other than the '8' shown here.<br />
<br />
raspberrypi:~ $ find / -name "crtbegin.o" 2>null<br />
/usr/lib/gcc/arm-linux-gnueabihf/8/crtbegin.o<br />
raspberrypi:~ $ (cd /usr/lib/gcc/arm-linux-gnueabihf/8 && tar czf $HOME/crt.tgz crt*.o)<br />
<br />
Take these three files, 248M, 7M and 3K respectively to the Intel box and put them in your home directory, then, we will deal with them as root further down.<br />
They will use up some 640M of disk and probably includes libraries you will never use but its easier than trying to work out exactly what you do need.<br />
<br />
=== Edit fpc.cfg ===<br />
'''Evidence is this step is unnecessary, just ignore it for now, further research is indicated'''<br />
Only one change is necessary, must tell the compiler which particular arm cpu we are targeting. <br />
<br />
#ifdef cpuarm<br />
-CpARMV6<br />
-CfVFPV2<br />
-OoFASTMATH<br />
#endif<br />
<br />
If your fpc.cfg is in /etc and it probably is, you will need to do that edit as root, thats OK, we are about to do a whole load of stuff as root. So, from now on, you will be running as root, that means great care must be used. Note these instructions are meant to be copied and pasted, one by one. They are not a script !<br />
<br />
=== sudo ===<br />
<br />
OK, here we must become root, that implies very careful ! Most linux systems allow sudo, if not, try su -<br />
<br />
If you do not have the root password (or don't want to use it), then reconside this whole operation, you can install the base compiler into user space from a tarball and build Lazarus from source, again in user space. Pretty good way to work, you will need to adjust the various paths accordingly. <br />
<br />
sudo -i<br />
<br />
=== Install the Pi Libraries ===<br />
<br />
Next, we need to install the needed libraries from a Pi from those tar balls we moved over before. The new libraries all end up in /usr/lib/arm-linux-gnueabihf - so easy clean up if necessary.<br />
<br />
cd / <br />
tar xzf /home/"$SUDO_USER"/usr_lib_arm-linux-gnueabihf.tgz <br />
tar xzf /home/"$SUDO_USER"/lib_arm-linux-gnueabihf.tgz<br />
tar xzf /home/"$SUDO_USER"/crt.tgz -C /usr/lib/arm-linux-gnueabihf <br />
<br />
Next, install some needed things and symlink them up to what Lazarus expects to find.<br />
<br />
apt install binutils-arm-linux-gnueabihf<br />
ln -s /usr/bin/arm-linux-gnueabihf-ld /usr/bin/arm-linux-ld<br />
ln -s /usr/bin/arm-linux-gnueabihf-as /usr/bin/arm-linux-as<br />
ln -s /usr/bin/arm-linux-gnueabihf-objcopy /usr/bin/arm-linux-objcopy<br />
ln -s /usr/bin/arm-linux-gnueabihf-strip /usr/bin/arm-linux-strip<br />
<br />
=== Build the Cross Compiler ===<br />
<br />
OK, now we build the cross compiler and a set of pre compiled arm units. <br />
<br />
<syntaxhighlight lang="bash"><br />
export FPCVER="3.2.2"<br />
cd /usr/share/fpcsrc/"$FPCVER"/<br />
<br />
make clean all FPMAKEOPT="-T 4" CPU_TARGET=arm OPT="-dFPC_ARMHF" CROSSOPT="-CpARMV6 -CaEABIHF" CROSSBINDIR=/usr/arm-linux-gnueabihf/bin<br />
<br />
make crossinstall OS_TARGET=linux CPU_TARGET=arm OPT="-dFPC_ARMHF" CROSSBINDIR=/usr/arm-linux-gnueabihf/bin CROSSOPT="-CpARMV6 -CaEABIHF" INSTALL_PREFIX=/usr<br />
<br />
ln -sf /usr/lib/fpc/"$FPCVER"/ppcrossarm /usr/bin/ppcrossarm</syntaxhighlight><br />
<br />
OK, done, Control-D to get out of that somewhat dangerous root access and test !<br />
<br />
=== Test ===<br />
<br />
Testing fpc functionality is easy, put the hello world source in a file, hello.pas and try this -<br />
<br />
db@U2104G-VirtualBox:~/Pascal/console$ ppcrossarm hello.pas<br />
Free Pascal Compiler version 3.2.2 [2021/07/19] for arm<br />
Copyright (c) 1993-2021 by Florian Klaempfl and others<br />
Target OS: Linux for ARMEL<br />
Compiling hello.pas<br />
Linking hello<br />
4 lines compiled, 0.1 sec<br />
<br />
db@U2104G-VirtualBox:~/Pascal/console$ file hello<br />
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.0.0, stripped<br />
<br />
You can move that binary over to a Pi and test it really works.<br />
<br />
Testing the Lazarus functionality is more involved, fire up Lazarus, open a new project, maybe set a control or two on it, open Project->ProjectOptions->ConfigAndTarget and set ProjectCPUFamily to 'arm'.<br />
<br />
Compile and see if the binary looks like this -<br />
<br />
db@U2104G-VirtualBox:~/Pascal/ArmTest$ file project1<br />
project1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.0.0, not stripped<br />
<br />
===Further Information===<br />
This page is about making '''armhf''' software, that suits arm chips using hardware floating point capabilities. The current Raspberry Pi is an example. The alternative is the '''armel''' or soft float arm chip, it too appears in many systems. It is possible that omitting the OPT="-dFPC_ARMHF" bit from the above command lines may make it work for soft float. That has not been tested !<br />
<br />
Binaries can be ArmV6 or ArmV7 (or even ArmV8 but different rules apply) in either armhf or armel instruction set. Generally soft float arm software may run on (eg) a Raspberry Pi but it may also confuse your package manager. Again, theoretical advice.<br />
<br />
I found the '''readelf''' command useful for looking at a resulting binary after compilation, try the following -<br />
<br />
readelf -a mybinary | grep Tag [enter]<br />
...<br />
readelf -h mybinary [enter]<br />
<br />
Another useful tip when playing in this space is to use Lazarus's Tools->RefreshFPCSource menu entry after making changes to the FPC config.<br />
<br />
== See Also ==<br />
<br />
* [[Cross_compiling|Cross Compiling]]<br />
* [[Installing_Lazarus|Installing Lazarus]]<br />
* [[Installing_the_Free_Pascal_Compiler|Installing the Free Pascal Compiler]]<br />
[[Category: Cross compilation]]<br />
[[Category: Linux]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Cross_Compile_to_RasPi_from_Linux&diff=153512Cross Compile to RasPi from Linux2022-08-16T16:09:46Z<p>Skalogryz: /* See Also */</p>
<hr />
<div><br />
<br />
=== Introduction ===<br />
<br />
This page is about setting up a Cross Compiler from a Linux Box to a [[Lazarus_on_Raspberry_Pi | Raspberry Pi]]. You should be able to select arm as the CPU in Lazarus or if just using FPC, choose to use the ppcrossarm compiler and produce v6 arm binaries. Raspberry Pi is just a specific example of arm, only small changes would seem necessary on other similar platforms.<br />
<br />
* Here we talk about making armhf software, not armel, there is a very brief discussion about that lower down.<br />
* Tested on an Ubuntu 21.04 and Debian Bullseye System. Its should be similar on most mainstream Linux platforms.<br />
* This recipe assumes '''you already have a current FPC''' and, possibly Lazarus installed. You MUST use packages downloaded from the FPC/Lazarus SourceForge site, its most likely that FPC and Lazarus packages from your Distribution will not work as expected. <br />
* A large part of this install is done as root. Working like that gives you the potential to make a real mess of your system, please be very careful.<br />
<br />
'''This page is under development and the recipe presented is still being tested.'''<br />
<br />
=== Getting the necessary libraries ===<br />
<br />
Firstly, we are going to need some reasonably current libraries from your Raspberry Pi, as near as I can determine this is the best way to get the necessary libraries. <br />
<br />
On the pi, we will tar up what we need, this does not need to be done as root.<br />
<br />
tar czf usr_lib_arm-linux-gnueabihf.tgz /usr/lib/arm-linux-gnueabihf<br />
tar czf lib_arm-linux-gnueabihf.tgz /lib/arm-linux-gnueabihf<br />
<br />
Next, we need the crt files, first, find where they are, yours may be in a directory other than the '8' shown here.<br />
<br />
raspberrypi:~ $ find / -name "crtbegin.o" 2>null<br />
/usr/lib/gcc/arm-linux-gnueabihf/8/crtbegin.o<br />
raspberrypi:~ $ (cd /usr/lib/gcc/arm-linux-gnueabihf/8 && tar czf $HOME/crt.tgz crt*.o)<br />
<br />
Take these three files, 248M, 7M and 3K respectively to the Intel box and put them in your home directory, then, we will deal with them as root further down.<br />
They will use up some 640M of disk and probably includes libraries you will never use but its easier than trying to work out exactly what you do need.<br />
<br />
=== Edit fpc.cfg ===<br />
'''Evidence is this step is unnecessary, just ignore it for now, further research is indicated'''<br />
Only one change is necessary, must tell the compiler which particular arm cpu we are targeting. <br />
<br />
#ifdef cpuarm<br />
-CpARMV6<br />
-CfVFPV2<br />
-OoFASTMATH<br />
#endif<br />
<br />
If your fpc.cfg is in /etc and it probably is, you will need to do that edit as root, thats OK, we are about to do a whole load of stuff as root. So, from now on, you will be running as root, that means great care must be used. Note these instructions are meant to be copied and pasted, one by one. They are not a script !<br />
<br />
=== sudo ===<br />
<br />
OK, here we must become root, that implies very careful ! Most linux systems allow sudo, if not, try su -<br />
<br />
If you do not have the root password (or don't want to use it), then reconside this whole operation, you can install the base compiler into user space from a tarball and build Lazarus from source, again in user space. Pretty good way to work, you will need to adjust the various paths accordingly. <br />
<br />
sudo -i<br />
<br />
=== Install the Pi Libraries ===<br />
<br />
Next, we need to install the needed libraries from a Pi from those tar balls we moved over before. The new libraries all end up in /usr/lib/arm-linux-gnueabihf - so easy clean up if necessary.<br />
<br />
cd / <br />
tar xzf /home/"$SUDO_USER"/usr_lib_arm-linux-gnueabihf.tgz <br />
tar xzf /home/"$SUDO_USER"/lib_arm-linux-gnueabihf.tgz<br />
tar xzf /home/"$SUDO_USER"/crt.tgz -C /usr/lib/arm-linux-gnueabihf <br />
<br />
Next, install some needed things and symlink them up to what Lazarus expects to find.<br />
<br />
apt install binutils-arm-linux-gnueabihf<br />
ln -s /usr/bin/arm-linux-gnueabihf-ld /usr/bin/arm-linux-ld<br />
ln -s /usr/bin/arm-linux-gnueabihf-as /usr/bin/arm-linux-as<br />
ln -s /usr/bin/arm-linux-gnueabihf-objcopy /usr/bin/arm-linux-objcopy<br />
ln -s /usr/bin/arm-linux-gnueabihf-strip /usr/bin/arm-linux-strip<br />
<br />
=== Build the Cross Compiler ===<br />
<br />
OK, now we build the cross compiler and a set of pre compiled arm units. <br />
<br />
<syntaxhighlight lang="bash"><br />
export FPCVER="3.2.2"<br />
cd /usr/share/fpcsrc/"$FPCVER"/<br />
<br />
make clean all FPMAKEOPT="-T 4" CPU_TARGET=arm OPT="-dFPC_ARMHF" CROSSOPT="-CpARMV6 -CaEABIHF" CROSSBINDIR=/usr/arm-linux-gnueabihf/bin<br />
<br />
make crossinstall OS_TARGET=linux CPU_TARGET=arm OPT="-dFPC_ARMHF" CROSSBINDIR=/usr/arm-linux-gnueabihf/bin CROSSOPT="-CpARMV6 -CaEABIHF" INSTALL_PREFIX=/usr<br />
<br />
ln -sf /usr/lib/fpc/"$FPCVER"/ppcrossarm /usr/bin/ppcrossarm</syntaxhighlight><br />
<br />
OK, done, Control-D to get out of that somewhat dangerous root access and test !<br />
<br />
=== Test ===<br />
<br />
Testing fpc functionality is easy, put the hello world source in a file, hello.pas and try this -<br />
<br />
db@U2104G-VirtualBox:~/Pascal/console$ ppcrossarm hello.pas<br />
Free Pascal Compiler version 3.2.2 [2021/07/19] for arm<br />
Copyright (c) 1993-2021 by Florian Klaempfl and others<br />
Target OS: Linux for ARMEL<br />
Compiling hello.pas<br />
Linking hello<br />
4 lines compiled, 0.1 sec<br />
<br />
db@U2104G-VirtualBox:~/Pascal/console$ file hello<br />
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.0.0, stripped<br />
<br />
You can move that binary over to a Pi and test it really works.<br />
<br />
Testing the Lazarus functionality is more involved, fire up Lazarus, open a new project, maybe set a control or two on it, open Project->ProjectOptions->ConfigAndTarget and set ProjectCPUFamily to 'arm'.<br />
<br />
Compile and see if the binary looks like this -<br />
<br />
db@U2104G-VirtualBox:~/Pascal/ArmTest$ file project1<br />
project1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.0.0, not stripped<br />
<br />
===Further Information===<br />
This page is about making '''armhf''' software, that suits arm chips using hardware floating point capabilities. The current Raspberry Pi is an example. The alternative is the '''armel''' or soft float arm chip, it too appears in many systems. It is possible that omitting the OPT="-dFPC_ARMHF" bit from the above command lines may make it work for soft float. That has not been tested !<br />
<br />
Binaries can be ArmV6 or ArmV7 (or even ArmV8 but different rules apply) in either armhf or armel instruction set. Generally soft float arm software may run on (eg) a Raspberry Pi but it may also confuse your package manager. Again, theoretical advice.<br />
<br />
I found the '''readelf''' command useful for looking at a resulting binary after compilation, try the following -<br />
<br />
readelf -a mybinary | grep Tag [enter]<br />
...<br />
readelf -h mybinary [enter]<br />
<br />
Another useful tip when playing in this space is to use Lazarus's Tools->RefreshFPCSource menu entry after making changes to the FPC config.<br />
<br />
== See Also ==<br />
<br />
* [[Cross_compiling|Cross Compiling]]<br />
* [[Installing_Lazarus|Installing Lazarus]]<br />
* [[Installing_the_Free_Pascal_Compiler|Installing the Free Pascal Compiler]]<br />
[[Category: Cross Compilation]]<br />
[[Category: Linux]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:HTML&diff=153511Category:HTML2022-08-16T16:07:45Z<p>Skalogryz: </p>
<hr />
<div>[[Category:SGML]]<br />
[[Category:File Format]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:XML&diff=153510Category:XML2022-08-16T16:07:32Z<p>Skalogryz: </p>
<hr />
<div>{{XML(ctg)}}<br />
<br />
[[Category:SGML]]<br />
[[Category:Data import and export]]<br />
[[Category:File Format]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:File_Format&diff=153509Category:File Format2022-08-16T16:07:13Z<p>Skalogryz: Created page with "any category that is dealing with a file format"</p>
<hr />
<div>any category that is dealing with a file format</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:SVG&diff=153508Category:SVG2022-08-16T16:06:26Z<p>Skalogryz: </p>
<hr />
<div>any page that's related or refers to SVG file format<br />
[[Category:File Format]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:SVG&diff=153507Category:SVG2022-08-16T16:05:59Z<p>Skalogryz: Created page with "any page that's related or refers to SVG file format"</p>
<hr />
<div>any page that's related or refers to SVG file format</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=LazPaint_SVG_format&diff=153506LazPaint SVG format2022-08-16T16:05:43Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>This is the specification of the SVG extensions used by LazPaint.<br />
<br />
=== Used terms ===<br />
<br />
A '''layer''' refers to a layer in an OpenRaster stack. They are combined together with an integer offset (x and y), a blending mode and an opacity.<br />
<br />
A '''group'''[https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g] is an SVG element that contains other elements. It can apply a transform to its content.<br />
<br />
A '''non trivial transform''' is a transform that is not a translation of integer coordinates.<br />
<br />
<br />
=== Layer transform ===<br />
<br />
When LazPaint stores an SVG layer and that this layer has a non trivial transform, it put all the elements of the SVG within a group in order to make the transform part of the SVG (rotation, scaling and skew).<br />
<br />
==== transform ====<br />
<br />
The '''transform''' attribute of the group stores a transform similar to the one applied to the layer, except that it can be translated along the x and y axes. The inverse translation is specified in the x and y attribute of the layer in the OpenRaster stack, so that the resulting transform matches the one applied to the layer.<br />
<br />
There is a translation because the bounding box of the transformed SVG is generally not the same as the resulting image, in particular when it overflows the image bounds. The SVG bounds is computed to fit the transformed view box.<br />
<br />
==== bgra:originalViewBox ====<br />
<br />
The original view box is stored in the '''bgra:originalViewBox''' attribute. For example, if the SVG had its custom coordinates going from 0 to 100, it would have the value "0 0 100 100". This allows to draw a highlight rectangle when moving the SVG layer showing the current rotation even though the SVG bounds have been widened.<br />
<br />
In order for the SVG to follow the specifications, there is a '''xmlns:bgra''' attribute giving an URL pointing to this specification.<br />
==See Also==<br />
* [[LazPaint]]<br />
[[Category:LazPaint]]<br />
[[Category:SVG]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=SVG_Image_List&diff=153505SVG Image List2022-08-16T16:05:33Z<p>Skalogryz: /* Optional parameter */</p>
<hr />
<div>Hi, this is a simple tutorial on how to use SVG Images in our Lazarus applications.<br />
<br />
== Requirements ==<br />
<br />
1) Install [[BGRABitmap]]<br />
<br />
2) Install [[BGRAControls]]<br />
<br />
(Hint: Use online package manager)<br />
<br />
== Tutorial ==<br />
<br />
In this tutorial we will add an SVG Icon to a TMainMenu.<br />
<br />
=== Create elements ===<br />
<br />
1) Drop a TMainMenu, add some entries like 'File' and a sub entry 'Open'.<br />
<br />
2) Drop a regular TImageList, assign it to the MainMenu1 we previously created.<br />
<br />
3) Assign the ImageIndex in the entries, for example in the File > Open add index 0.<br />
<br />
That is not different as you need to do it anyways with a regular image list, so nothing changes here. The next step shows the SVG stuff.<br />
<br />
=== Create SVG Image List ===<br />
<br />
1) Add a TBGRASVGImageList from BGRA Themes pallete.<br />
<br />
2) Double click the SVG Image List component and add some SVG's, just load them from file.<br />
<br />
3) Add this code, the glue between our two image lists:<br />
<br />
<syntaxhighlight lang="pascal"><br />
procedure TForm1.FormCreate(Sender: TObject);<br />
begin<br />
BGRASVGImageList1.PopulateImageList(ImageList1, [24]);<br />
end; <br />
</syntaxhighlight><br />
<br />
'''Done!'''<br />
<br />
Now you have SVG Icons in your application, repeat for buttons and any component that supports a regular image list.<br />
<br />
=== Optional parameter ===<br />
<br />
The second parameter is array of resolutions, 24px is the default on Windows. You can add more like 48px for retina on macOS or High DPI on Windows.<br />
<br />
<syntaxhighlight lang="pascal"><br />
BGRASVGImageList1.PopulateImageList(ImageList1, [24, 48]);<br />
</syntaxhighlight><br />
<br />
'''Important:''' You need to enable 'Scaled' property on the regular image list in order to the extra resolutions work fine.<br />
<br />
Enjoy!<br />
[[Category:SVG]]<br />
[[Category:Graphics]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=LazPaint_SVG_format&diff=153504LazPaint SVG format2022-08-16T16:04:51Z<p>Skalogryz: /* bgra:originalViewBox */</p>
<hr />
<div>This is the specification of the SVG extensions used by LazPaint.<br />
<br />
=== Used terms ===<br />
<br />
A '''layer''' refers to a layer in an OpenRaster stack. They are combined together with an integer offset (x and y), a blending mode and an opacity.<br />
<br />
A '''group'''[https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g] is an SVG element that contains other elements. It can apply a transform to its content.<br />
<br />
A '''non trivial transform''' is a transform that is not a translation of integer coordinates.<br />
<br />
<br />
=== Layer transform ===<br />
<br />
When LazPaint stores an SVG layer and that this layer has a non trivial transform, it put all the elements of the SVG within a group in order to make the transform part of the SVG (rotation, scaling and skew).<br />
<br />
==== transform ====<br />
<br />
The '''transform''' attribute of the group stores a transform similar to the one applied to the layer, except that it can be translated along the x and y axes. The inverse translation is specified in the x and y attribute of the layer in the OpenRaster stack, so that the resulting transform matches the one applied to the layer.<br />
<br />
There is a translation because the bounding box of the transformed SVG is generally not the same as the resulting image, in particular when it overflows the image bounds. The SVG bounds is computed to fit the transformed view box.<br />
<br />
==== bgra:originalViewBox ====<br />
<br />
The original view box is stored in the '''bgra:originalViewBox''' attribute. For example, if the SVG had its custom coordinates going from 0 to 100, it would have the value "0 0 100 100". This allows to draw a highlight rectangle when moving the SVG layer showing the current rotation even though the SVG bounds have been widened.<br />
<br />
In order for the SVG to follow the specifications, there is a '''xmlns:bgra''' attribute giving an URL pointing to this specification.<br />
==See Also==<br />
* [[LazPaint]]<br />
[[Category:LazPaint]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=ascii85&diff=153503ascii852022-08-16T16:03:19Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>Ascii85, also called Base85, is a form of binary-to-text encoding developed by Paul E. Rutter for the “btoa” utility. By using five ASCII characters to represent four bytes of binary data (making the encoded size ¹⁄₄ larger than the original, assuming eight bits per ASCII character), it is more efficient than uuencode or Base64, which use four characters to represent three bytes of data (¹⁄₃ increase, assuming eight bits per ASCII character).<br />
<br />
Its main modern use is in Adobeʼs PostScript and Portable Document Format file formats.<br />
<br />
==See Also==<br />
* https://www.freepascal.org/docs-html/fcl/ascii85/index.html<br />
* https://en.wikipedia.org/wiki/Ascii85<br />
<br />
[[Category: FCL]]<br />
[[Category:Packages]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=ascii85&diff=153502ascii852022-08-16T16:03:11Z<p>Skalogryz: </p>
<hr />
<div>Ascii85, also called Base85, is a form of binary-to-text encoding developed by Paul E. Rutter for the “btoa” utility. By using five ASCII characters to represent four bytes of binary data (making the encoded size ¹⁄₄ larger than the original, assuming eight bits per ASCII character), it is more efficient than uuencode or Base64, which use four characters to represent three bytes of data (¹⁄₃ increase, assuming eight bits per ASCII character).<br />
<br />
Its main modern use is in Adobeʼs PostScript and Portable Document Format file formats.<br />
<br />
==See Also==<br />
* https://www.freepascal.org/docs-html/fcl/ascii85/index.html<br />
* https://en.wikipedia.org/wiki/Ascii85<br />
<br />
[[Categories FCL]]<br />
[[Category:Packages]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=pas2js_Electron_Web_Application&diff=153501pas2js Electron Web Application2022-08-16T15:57:45Z<p>Skalogryz: /* Lazarus Electron Web Application */</p>
<hr />
<div>= Electron Web Application =<br />
<br />
Since Lazarus 2.3 and pas2js 2.2.1.<br />
<br />
=Install Electron=<br />
<br />
First you must install '''node.js'''.<br />
<br />
* Windows, MacOS: https://nodejs.org/en/download/<br />
* Debian, Ubuntu:<br />
sudo apt install nodejs npm<br />
<br />
Check that node and npm work:<br />
node -v<br />
npm -v<br />
<br />
Create some directory and install electron there:<br />
mkdir mynodejs<br />
cd mynodejs<br />
npm install --save-dev electron<br />
<br />
This should create a sub folder ''node_modules'' containing electron:<br />
C:\Whatever\mynodejs\node_modules\electron\dist\electron.exe<br />
<br />
Set the full path to this executable in ''Lazarus Tools / Options / pas2js / Electron executable''<br />
<br />
=Lazarus Electron Web Application=<br />
<br />
Install pas2jsdgn package. For more information see [[lazarus_pas2js_integration|here]].<br />
<br />
You get a new project type '''Electron Web Application''', which is like a ''Web Browser Application'', except this wizard creates three projects, the '''preload.lpi''', the '''render.lpi''' and '''YourApp.lpi''', and a project group. <br />
<br />
After the options dialog it asks for the file name of the webapp program source filename (usually a *.lpr or *.pas). Choose some name, you can later rename it.<br />
<br />
To run it you must compile all three projects to create the three needed js files. The project group window helps you to switch easily and compile all projects.<br />
[[Category:Applications created with Lazarus]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Pas2js_Transpiler&diff=153500Pas2js Transpiler2022-08-16T15:55:24Z<p>Skalogryz: /* Creating source maps */</p>
<hr />
<div>==About pas2js==<br />
<br />
pas2js is a compiler/transpiler to translate programs written in Pascal (subset of Delphi/ObjFPC syntax) to JavaScript.<br /> The goal is to use strong typing, while still be able to use low level whenever you choose.<br /> The compiled Pascal functions can be used in DOM events or called by JavaScript.<br /> pas2js is written completely in FPC, runs on many platforms like Windows, Mac and Linux and more. It is built modular consisting of the following parts:<br />
<br />
* file cache - loading, caching files, converting to UTF-8<br />
* file resolver - handling search paths, finding used units and include files<br />
* scanner - reading tokens, handling compiler directives like $IfDef and $Include<br />
* parser - reading the tokens, checking syntax, creating Pascal nodes<br />
* resolver - resolving references, type checking and checking duplicate identifiers<br />
* use analyzer - finding unused identifiers, emit hints and warning<br />
* converter - translating Pascal nodes into JavaScript nodes<br />
* compiler - handling config files, parameters, compiling recursively all used units, writes js<br />
* command line interface - a small wrapper to embed the compiler into a console program<br />
* library and interface - a small wrapper to embed the compiler into a library<br />
<br />
Each part is tested separately and is used by other FPC tools as well. For example the scanner and parser are used by fpdoc too. Thus they are tested and extended by other programmers, reducing greatly the work for developing pas2js. Consistency is kept by several test suites, containing thousands of tests. </div><div class="section"> Note: The modular structure allows to compile any parts or the whole compiler into an IDE addon (not yet started). </div><div class="section"><br />
<br />
==Command line parameters==<br />
<br />
Most parameters work the same as their FPC equivalent. pas2js has some options of its own (see -J options).<br />
<br />
<pre> <br />
Options:<br />
Put + after a boolean switch option to enable it, - to disable it<br />
@<x> : Read compiler options from file <x> in addition to the default<br />
pas2js.cfg<br />
-B : Rebuild all<br />
-d<x> : Defines the symbol <x>. Optional: -d<x>:=<value><br />
-i<x> : Write information and halt. <x> is a combination of the following:<br />
-iD : Write compiler date<br />
-iSO : Write compiler OS<br />
-iSP : Write compiler host processor<br />
-iTO : Write target platform<br />
-iTP : Write target processor<br />
-iV : Write short compiler version<br />
-iW : Write full compiler version<br />
-ic : Write list of supported JS processors usable by -P<x><br />
-im : Write list of supported modeswitches usable by -M<x><br />
-io : Write list of supported optimizations usable by -Oo<x><br />
-it : Write list of supported targets usable by -T<x><br />
-iJ : Write list of supported JavaScript identifiers -JoRTL-<x><br />
-C<x> : Code generation options. <x> is a combination of the following<br />
letters:<br />
o : Overflow checking of integer operations<br />
r : Range checking<br />
R : Object checks. Verify method calls and object type casts.<br />
-F... Set file names and paths:<br />
-Fe<x>: Redirect output to file <x>. UTF-8 encoded.<br />
-FE<x>: Set main output path to <x><br />
-Fi<x>: Add <x> to include paths<br />
-FN<x>: add <x> to namespaces. Namespaces with trailing - are removed.<br />
Delphi calls this flag "unit scope names".<br />
-Fu<x>: Add <x> to unit paths<br />
-FU<x>: Set unit output path to <x><br />
-I<x> : Add <x> to include paths, same as -Fi<br />
-J... Extra options of pas2js<br />
-Ja<x>: Append JS file <x> to main JS file. E.g. -Jamap.js. Can be given<br />
multiple times. To remove a file name append a minus, e.g.<br />
-Jamap.js-.<br />
-Jc : Write all JavaScript concatenated into the output file<br />
-Je<x>: Encode messages as <x>.<br />
-Jeconsole: Console codepage. This is the default.<br />
-Jesystem : System codepage. On non Windows console and system are the<br />
same.<br />
-Jeutf-8 : Unicode UTF-8. Default when using -Fe.<br />
-JeJSON : Output compiler messages as JSON. Logo etc are outputted<br />
as-is.<br />
-Ji<x>: Insert JS file <x> into main JS file. E.g. -Jirtl.js. Can be given<br />
multiple times. To remove a file name append a minus, e.g.<br />
-Jirtl.js-.<br />
-Jl : lower case identifiers<br />
-Jm : generate source maps<br />
-Jmsourceroot=<x>: use x as "sourceRoot", prefix URL for source file<br />
names.<br />
-Jmbasedir=<x>: write source file names relative to directory x, default<br />
is map file folder.<br />
-Jminclude: include Pascal sources in source map.<br />
-Jmabsolute: store absolute filenames, not relative.<br />
-Jmxssiheader: start source map with XSSI protection )]}', default.<br />
-Jm-: disable generating source maps<br />
-Jo<x>: Enable or disable extra option. The x is case insensitive:<br />
-JoSearchLikeFPC: search source files like FPC, default: search case<br />
insensitive.<br />
-JoUseStrict: add "use strict" to modules, default.<br />
-JoCheckVersion-: do not add rtl version check, default.<br />
-JoCheckVersion=main: insert rtl version check into main.<br />
-JoCheckVersion=system: insert rtl version check into system unit init.<br />
-JoCheckVersion=unit: insert rtl version check into every unit init.<br />
-JoRTL-<y>=<z>: set RTL identifier y to value z. See -iJ.<br />
-Jr<x> Control writing of resource string file<br />
-Jrnone: Do not write resource string file<br />
-Jrunit: Write resource string file per unit with all resource strings<br />
-Jrprogram: Write resource string file per program with all used<br />
resource strings in program<br />
-JR<x> Control writing of linked resources<br />
-JRnone: Skip resource directives<br />
-JRjs: Write resources in Javascript structure<br />
-JRhtml[=filename] : Write resources as preload links in HTML file <br />
(default is projectfile-res.html)<br />
-Jpcmd<command>: Run postprocessor. For each generated js execute command<br />
passing the js as stdin and read the new js from stdout. This<br />
option can be added multiple times to call several postprocessors<br />
in succession.<br />
-Ju<x>: Add <x> to foreign unit paths. Foreign units are not compiled.<br />
-JU: This pas2js does not support PCU files<br />
-l : Write logo<br />
-M<x> : Set language mode or enable/disable a modeswitch<br />
-MDelphi: Delphi 7 compatibility mode<br />
-MObjFPC: FPC's Object Pascal compatibility mode (default)<br />
-M<x> : enable or disable modeswitch, see option -im<br />
Each mode (as listed above) enables its default set of modeswitches.<br />
Other modeswitches are disabled and need to be enabled one by another.<br />
-NS<x> : obsolete: add <x> to namespaces. Same as -FN<x><br />
-n : Do not read the default config files<br />
-o<x> : Change main JavaScript file to <x>, "." means stdout<br />
-O<x> : Optimizations:<br />
-O- : Disable optimizations<br />
-O1 : Level 1 optimizations (quick and debugger friendly)<br />
-O2 : Level 2 optimizations (Level 1 + not debugger friendly)<br />
-Oo<x>: Enable or disable optimization. The x is case insensitive:<br />
-OoEnumNumbers[-]: write enum value as number instead of name. Default<br />
in -O1.<br />
-OoRemoveNotUsedPrivates[-]: Default is enabled<br />
-OoRemoveNotUsedDeclarations[-]: Default enabled for programs with -Jc<br />
-OoRemoveNotUsedPublished[-] : Default is disabled<br />
-OoShortRefGlobals[-]: Insert JS local var for types, modules and<br />
static functions. Default enabled in -O2<br />
-P<x> : Set target processor. Case insensitive:<br />
-Pecmascript5: default<br />
-Pecmascript6<br />
-S<x> : Syntax options. <x> is a combination of the following letters:<br />
2 : Same as -Mobjfpc (default)<br />
a : Turn on assertions<br />
c : Support operators like C (*=,+=,/= and -=)<br />
d : Same as -Mdelphi<br />
j : Allows typed constants to be writeable (default)<br />
m : Enables macro replacements<br />
-SI<x> : Set interface style to <x><br />
-SIcom : COM, reference counted interface (default)<br />
-SIcorba: CORBA interface<br />
-T<x> : Set target platform<br />
-Tbrowser: default<br />
-Tnodejs : add pas.run(), includes -Jc<br />
-Telectron: experimental<br />
-Tmodule : add pas.run(), includes -Jc<br />
-u<x> : Undefines the symbol <x><br />
-v<x> : Be verbose. <x> is a combination of the following letters:<br />
e : Show errors (default)<br />
w : Show warnings<br />
n : Show notes<br />
h : Show hints<br />
i : Show info<br />
l : Show line numbers, needs -vi<br />
a : Show everything<br />
0 : Show nothing (except errors)<br />
b : Show file names with full path<br />
c : Show conditionals<br />
t : Show tried/used files<br />
d : Show debug notes and info, enables -vni<br />
q : Show message numbers<br />
x : Show used tools<br />
v : Write pas2jsdebug.log with lots of debugging info<br />
z : Write messages to stderr, -o. still uses stdout.<br />
-vm<x>,<y>: Do not show messages numbered <x> and <y>.<br />
-? : Show this help<br />
-h : Show this help<br />
<br />
Environment variable PAS2JS_OPTS is parsed after default config and before<br />
command line parameters.<br />
</pre><br />
<br />
==Delphi and ObjFPC mode==<br />
<br />
===Delphi mode===<br />
<br />
* Defines macro ''DELPHI''<br />
* Assigning a function to a function type variable does not require the @ operator. For example, you can write either ''OnGetThing:=GetValue;'' or ''OnGetThing:=@GetValue;''.<br />
* A function type variable reference without brackets is treated as a call. For example: If ''OnGetThing'' is a variable of type ''function: integer'' you can write: ''If OnGetThing=3 then ;''.<br />
* You must use the @@ operator to get the procedure address (i.e. JS reference) of a procedure type variable. For example instead of ''If OnClick=nil then ;'' you must use ''if @@OnClick=nil then ;''.<br />
* Every procedure/method overload needs the 'overload' modifier.<br />
<br />
===ObjFPC mode===<br />
<br />
This the default mode of pas2js and is generally more strict than the Delphi mode, and allows some more operations.<br />
<br />
* Defines macro ''OBJFPC''<br />
* Assigning a function to a function type variable requires the @ operator. For example: ''OnGetThing:=@GetValue;''.<br />
* A function type variable always needs brackets to be called. For example: If ''OnGetThing'' is a variable of type ''function: integer'' then this is allowed: ''If OnGetThing()=3 then ;''. While this gives an error: ''If OnGetThing=3 then ;''.<br />
* You can compare a procedure type with ''nil''. For example ''If OnClick=nil then ;''.<br />
* You can compare a procedure type with a procedure address (i.e. JS reference). For example ''If OnClick=@OnFormClick then ;''.<br />
* The procedure modifier 'overload' can be omitted when all overloads are in one scope, e.g. a unit or a class. And if one procedure has such modifier all procedures with same name and in same scope are overloads as well.<br />
<br />
</div><div class="section"><br />
<br />
==Translating modules==<br />
<br />
A Pascal Program is translated into the following JavaScript structure:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript Structure, not code!<br />
|-<br />
|<br />
<pre><br />
Program &lt;unitname&gt;;<br />
Implementation<br />
[implementation section]<br />
Begin<br />
[main code]<br />
End.<br />
</pre><br />
|<br />
<pre><br />
pas.&lt;program&gt;={<br />
[implementation section],<br />
$main: function() {<br />
[main code]<br />
}<br />
};<br />
</pre><br />
|}<br />
<br />
A Pascal Unit is translated into the following JavaScript structure:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript Structure, not code!<br />
|-<br />
|<br />
Unit &lt;unitname&gt;;<br />
Interface<br />
[interface section]<br />
Implementation<br />
[implementation section]<br />
Initialization<br />
[initialization section]<br />
End.<br />
|<br />
pas.&lt;unitname&gt;={<br />
[interface section],<br />
$impl: {<br />
[implementation section],<br />
},<br />
$init: function() {<br />
[initialization section]<br />
}<br />
};<br />
|}<br />
<br />
Note: The '''finalization''' section is not supported by pas2js.<br /></div><div class="section"> To create and initialize the units in topological order the compiler translates an Unit to the following JavaScript code:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit &lt;unitname&gt;;<br />
Interface<br />
[interface section]<br />
Implementation<br />
[implementation section]<br />
Initialization<br />
[initialization section]<br />
End.<br />
|<br />
rtl.module('&lt;unitname&gt;',<br />
['system',...other used units of the interface section...],<br />
function(){<br />
var $mod = this;<br />
var $impl = $mod.$impl;<br />
[interface section]<br />
$mod.$implcode = function(){<br />
[implementation section]<br />
}<br />
$mod.$init = function(){<br />
[initialization section]<br />
};<br />
},<br />
[...used units of the implementation section]<br />
};<br />
|}<br />
<br />
Here is a more detailed example to make it more clear:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses Sysutils;<br />
var<br />
dIntf: double;<br />
sIntf: string = 'abc';<br />
procedure MyIntfProc;<br />
Implementation<br />
Uses Classes;<br />
Var dImpl:double;<br />
Procedure MyIntfProc;<br />
Begin<br />
dImpl:=dIntf;<br />
End;<br />
Procedure MyImplProc;<br />
Begin<br />
dImpl:=dIntf;<br />
End;<br />
Initialization<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","SysUtils"],<br />
function(){<br />
var $mod = this;<br />
var $impl = $mod.$impl;<br />
this.dIntf = 0.0;<br />
this.sIntf = "abc";<br />
this.MyIntfProc = function(){<br />
$impl.dImpl = $mod.dIntf;<br />
};<br />
$mod.$implcode = function(){<br />
$impl.dImpl = 0.0;<br />
$impl.MyImplProc = function() {<br />
$impl.dImpl = $mod.dIntf;<br />
};<br />
}<br />
$mod.$init = function() {<br />
};<br />
},<br />
["Classes"]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Unit ''System'' is always loaded implicitely.<br />
* References to other units are translated to full path. For example ''TObject'' is translated to ''pas.system.TObject''<br />
* References to dotted unitnames, aka units with namespaces are translated to ''pas["namespace.unitname"]''.<br />
<br />
<br />
<br />
==Translating variables==<br />
<br />
Variables are converted without type, because JavaScript lacks a clear type. They are however always initialized, which helps JavaScript engines to optimize.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses Classes,Forms;<br />
const<br />
c1:integer=3;<br />
c2 = 'abc';<br />
c3 = 234;<br />
c4 = 12.45;<br />
c5 = nil;<br />
var<br />
v1:string;<br />
v2,v3:double;<br />
v4:byte=0;<br />
v5:TForm;<br />
v6:TIdentMapEntry;<br />
v7:string='abc';<br />
v8:char='c';<br />
v9:array of byte;<br />
Implementation<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","Classes","Forms"],<br />
function(){<br />
this.c1 = 3;<br />
this.c2 = "abc";<br />
this.c3 = 234;<br />
this.c4 = 12.45;<br />
this.c5 = null;<br />
this.v1 = "";<br />
this.v2 = 0.0;<br />
this.v3 = 0.0;<br />
this.v4 = 0;<br />
this.v5 = null;<br />
this.v6 = new pas.Classes.TIdentMapEntry();<br />
this.v7 = "abc";<br />
this.v8 = "c";<br />
this.v9 = [];<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Type casting a ''boolean'' to ''integer'', gives ''0'' for ''false'' and ''1'' for ''true''.<br />
* Type casting an ''integer'' to ''boolean'', gives ''false'' for ''0'' and ''true'' otherwise.<br />
* A '''char''' is translated to a JS string, because JS lacks a native char type.<br />
* A '''char''' is a single JS char code. An UTF-16 codepoint can contain one or two '''char'''.<br />
* '''Integers overflows''' at runtime differ from Delphi/FPC, due to the double format. For example adding ''var i: byte = 200; ... i:=i+100;'' will result in ''i=300'' instead of ''i=44'' as in Delphi/FPC. When range checking ''{$R+}'' is enabled ''i:=300'' will raise an ERangeError.<br />
* '''type cast integer to integer''', e.g. ''byte(aLongInt)''<br />
** with range checking enabled: error if outside range<br />
** without range checking: emulates the FPC/Delphi behaviour: e.g. ''byte(value)'' translates to ''value &amp; 0xff'', ''shortint(value)'' translates to ''value &amp; 0xff &lt;&lt;24 &gt;&gt; 24.''<br />
* The '''mod-operator''' works 32-bit signed in JS.<br />
<br />
==Translating string==<br />
<br />
Strings are translated to JavaScript strings. They are initialized with "" and are never '''null'''.<br /> There are no ''ShortString, AnsiString or RawByteString''. ''Unicodestring'' and ''Widestring'' are alias of ''String''.<br /> JavaScript strings are immutable, which means that changing a single character in a string, creates a new string. So a ''s[2]:='c';'' is a slow operation in pas2js compared to Delphi/FPC.<br /> Although pas2js creates .js files encoded as UTF-8 with BOM, JavaScript strings are UTF-16 at runtime. Keep in mind that one UTF-16 codepoint can need two ''char'', and a visible glyph can need several codepoints. Same as in Delphi.<br />
<br />
==Translating resourcestrings==<br />
<br />
Resourcestrings are translated to JS objects with original (org) and current value.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
resourcestring<br />
rsCompiler = 'pas2js';<br />
var<br />
s:string;<br />
Implementation<br />
initialization<br />
s:=rsCompiler;<br />
End.<br />
|<br />
rtl.module("test1",["System"],function () {<br />
var $mod = this;<br />
this.s = "";<br />
$mod.$resourcestrings = {rsCompiler: {org: "pas2js"}};<br />
$mod.$init = function () {<br />
$mod.s = rtl.getResStr(pas.test1,"rsCompiler");<br />
};<br />
});<br />
|}<br />
<br />
<br />
<br />
==Translating currency==<br />
<br />
''Currency'' in Delphi/FPC is an int64 with a factor of 10000. This is translated to a double with factor 10000 and truncated.<br />
<br />
* ''CurA := 1.12345'' -&gt; ''CurA = 11234''<br />
* ''CurA + CurB'' -&gt; ''CurA + CurB''<br />
* ''CurA * CurB'' -&gt; ''CurA * CurB/10000''<br />
* ''CurA / CurB'' -&gt; ''Math.floor(CurA/CurB * 10000)''<br />
* ''CurA ^^ CurB'' -&gt; ''Math.floor(Math.pow(CurA/10000,CurB/10000) * 10000)''<br />
* ''Currency + Double'' -&gt; ''Currency + (Double*10000)''<br />
* ''Double := Currency'' -&gt; ''Double = Currency/10000''<br />
* ''Currency := Double'' -&gt; ''Currency = Math.floor(Double*10000)''<br />
* ''JSValue := Currency'' -&gt; ''JSValue = Currency/10000''<br />
* Keep in mind that a double has only 54 bits for the number, so calculating values greater than 900,719,925,474 might give a different result than in Delphi/FPC. See SysUtils.MinCurrency/MaxCurrency<br />
<br />
<br />
<br />
==Translating Types==<br />
<br />
JavaScript type design has no declarative form, except for object types (so-called prototypes). That's why all the derivatives from simple Pascal types can not be translated. The compiler ensures type safety at compile time though, which is a big plus for using Pascal.<br /> Complex Pascal types (classes, records, or arrays) are translated into JavaScript objects or arrays respectively.<br /><br />
<br />
==Translating pointer==<br />
<br />
A ''pointer'' is translated to a JS reference. It can be assigned a class, a class instance, a class-of, an array, a procedure var, a method var, a @proc address, a @method address, or a pointer of record. There is no pointer arithmetic, i.e. no p+1, and no typed pointers, except for pointer of record. You can find out its type using the functions ''isArray'', ''isClass'', ''isClassRef'', ''isCallback'', etc of unit ''JS''.<br />
<br />
==Translating record type==<br />
<br />
A record is translated to a JavaScript object.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JS Pas2js 1.3<br />
! JS Pas2js 1.2<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TMyRecord = Record<br />
i: integer;<br />
s: string;<br />
d: TDateTime;<br />
End;<br />
Var<br />
r, s: TMyRecord;<br />
Implementation<br />
Initialization<br />
r.i := 123;<br />
r:=s;<br />
if r=s then ;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.recNewT($mod, "TMyRecord", function() {<br />
this.i = 0;<br />
this.s = "";<br />
this.d = 0.0;<br />
this.$eq = function (b) {<br />
return (this.i == b.i) &amp;&amp;<br />
(this.s == b.i) &amp;&amp; (this.d == b.d);<br />
};<br />
this.$assign = function (s) {<br />
this.i = s.i;<br />
this.s = s.s;<br />
this.d = s.d;<br />
return this;<br />
};<br />
};<br />
this.r = this.TMyRecord.$new();<br />
$mod.$init = function() {<br />
$mod.r.i=123;<br />
$mod.r.$assign($mod.s);<br />
if ($mod.r.$eq($mod.s)) ;<br />
},<br />
},<br />
[]);<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TMyRecord = function(s) {<br />
if (s){<br />
this.i = s.i;<br />
this.s = s.s;<br />
this.d = s.d;<br />
} else {<br />
this.i = 0;<br />
this.s = "";<br />
this.d = 0.0;<br />
};<br />
this.$equal = function (b) {<br />
return (this.i == b.i) &amp;&amp;<br />
(this.s == b.i) &amp;&amp; (this.d == b.d);<br />
};<br />
};<br />
this.r = new this.TMyRecord();<br />
$mod.$init = function() {<br />
$mod.r.i=123;<br />
$mod.r = new $mod.TMyRecord($mod.s);<br />
if ($mod.r.$equal($mod.s)) ;<br />
},<br />
},<br />
[]);<br />
|}<br />
<br />
* The record variable creates a JavaScript object.<br />
* Variant records are not supported.<br />
* Supported: Assign, pass as argument, equal, not equal, array of record, pointer of record, const, default(), RTTI.<br />
* Advanced record (since pas2js 1.3):<br />
** visibility private, strict private, public, default is public<br />
** methods, class methods (must be static like in Delphi/FPC)<br />
** class vars<br />
** const fields<br />
** property, class property, array property, default array property<br />
** sub types<br />
** constructor<br />
** class constructor<br />
* Not yet implemented:<br />
** operator overloading<br />
** reference counted interfaces as fields<br />
** Interfaces as nested types<br />
** default non array property<br />
* Until Pas2js 1.2 when assigning a record it is cloned, creating a new JS object. Since Pas2js 1.3 only values are copied, keeping the object, so pointer of record is compatible.<br />
* Since record types are JS objects it is possible to typecast a record type to the JS Object, e.g. ''TJSObject(TPoint)''. Note that you cannot typecast directly to a ''TJSObject'' descendant. You can use ''TJSWindow(TJSObject(aRecord))''.<br />
* A pointer of record is simply a reference.<br />
** ''p:=@r'' translates to ''p=r''<br />
** ''p^.x'' becomes ''p.x''.<br />
** ''New(PointerOfRecord)'' creates a new record<br />
** ''Dispose(PointerOfRecord)'' Sets the variable to null if possible.<br />
* <nowiki>Passing a record to an untyped arguments (e.g. ''TObject.Dispatch(var Msg)'') passes the record JS object directly, not creating a temporary reference object.</nowiki><br />
* Typecasting RecordType(UntypedArgument) returns the argument, i.e. no conversion.<br />
<br />
<br />
<br />
==Translating functions==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function DoubleIt(n: integer): integer;<br />
Implementation<br />
Function DoubleIt(n: integer): integer;<br />
Begin<br />
Result:=2*n;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.DoubleIt = function(n){<br />
Result = 0;<br />
Result = 2*n;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Local variables become local JavaScript variables: ''var l = 0;''.<br />
* Local constants become JavaScript variables in the unit/program implementation section.<br />
* Local types are elevated to module.<br />
* Overloaded functions are given an unique name by appending $1, $2, ...<br />
* Supported: default values, const/var/out/default, FuncName:=<br />
<br />
<br />
<br />
==Translating passing a parameter by reference==<br />
<br />
JavaScript lacks passing by reference. Instead a temporary object is created with a ''get'' and ''set'' function. That means changes within the procedure are immediately visible outside, compatible with Pascal.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure DoubleIt(var n: integer);<br />
Begin<br />
n:=2*n;<br />
End;<br />
Function Doubling(n: integer): integer;<br />
Begin<br />
DoubleIt(n);<br />
Result:=n;<br />
End;<br />
Var<br />
i: integer = 7;<br />
Begin<br />
Doubling(i);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 7;<br />
this.DoubleIt = function(n){<br />
n.set(2*n.get());<br />
};<br />
this.Doubling = function(n){<br />
var Result = 0;<br />
DoubleIt({<br />
get:function(){<br />
return n<br />
},<br />
set:function(v){<br />
n=v;<br />
}<br />
});<br />
Result = n;<br />
return n;<br />
};<br />
$mod.$main = function(){<br />
Doubling($mod.i);<br />
}<br />
},<br />
[]);<br />
|}<br />
<br />
When the passed value is from another context, the context is passed too:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure DoubleIt(var n: integer);<br />
Begin<br />
n:=2*n;<br />
End;<br />
Var<br />
i: integer = 7;<br />
Begin<br />
DoubleIt(i);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 7;<br />
this.DoubleIt = function(n){<br />
n.set(2*n.get());<br />
};<br />
$mod.$main = function(){<br />
DoubleIt({<br />
p:$mod,<br />
get:function(){<br />
return this.p.i<br />
},<br />
set:function(v){<br />
this.p.i=v;<br />
}<br />
});<br />
}<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Contrary to Delphi/FPC it is allowed to pass a property to a var/out parameter.<br />
<br />
<br />
<br />
==Translating nested functions==<br />
<br />
A nested function is translated to a local variable.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n, Adder: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n, Adder: integer): integer;<br />
<br />
Function Add(k: integer): integer;<br />
Begin<br />
if k=1 then<br />
Result:=1<br />
else<br />
Result:=Add(k-1)+Adder;<br />
End;<br />
<br />
Begin<br />
Result:=Add(n);<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.DoubleIt = function(n,Adder){<br />
Result = 0;<br />
var Add = function(k) {<br />
Result = 0;<br />
if (k==1) {<br />
Result = 1;<br />
} else {<br />
Result = Add(k-1)+Adder;<br />
}<br />
return Result;<br />
};<br />
Result = Add(n);<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: You can assign a nested procedure to a procedure variable. A nested procedure of a method can be assigned to a method variable.<br /> JavaScript preserves the current local scope, including references to the local variables of parent functions. Local types and constants belong to the unit scope (singleton).<br /> When a method has nested functions, the compiler adds a local var ''Self''.<br />
<br />
==Translating for-loops==<br />
<br />
The JavaScript for-loop executes the end expression every iteration, while Pascal only executes it once. Therefore a local variable is introduced. If the loop is not entered at all, the variable is not touched. If the loop was entered the variable contanis the last value.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
For i:=1 To n Do<br />
Begin<br />
j:=j+i;<br />
End;<br />
if i&lt;1 then j:=1;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
Result = 0;<br />
j = 0;<br />
for (var $l1 = 1, $le2 = n; $l1 &lt;= $le2; $l1++) {<br />
i = $l1;<br />
j = j + i;<br />
};<br />
if (i&lt;1) j=1;<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: The after-loop decrement is only added if ''i'' is read after the loop.<br /><br />
<br />
==Translating repeat..until==<br />
<br />
The ''repeat..until'' is translated to a ''do{}while()''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
i:=0;<br />
Repeat<br />
i:=i+1;<br />
j:=j+i;<br />
Until i&gt;=n;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
Result = 0;<br />
j = 0;<br />
i = 0;<br />
do{<br />
i = (i + 1);<br />
j = (j + i);<br />
} while (!(i&gt;=n));<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating while..do==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
i:=0;<br />
While i&lt;n Do Begin<br />
i:=i+1;<br />
j:=j+i;<br />
End;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
var Result = 0;<br />
var j = 0;<br />
var i = 0;<br />
while(i&lt;n){<br />
i = (i + 1);<br />
j = (j + i);<br />
};<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating case..do==<br />
<br />
Although JavaScript has something similar in form of the "switch" statement, it lacks ranges and is on current JS engines often slower than "if-else". Therefore a case..of is translated to if..else.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Var<br />
i: integer;<br />
Begin<br />
case i of<br />
1: ;<br />
2: i:=3;<br />
else<br />
i:=4;<br />
end;<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 0;<br />
$mod.$main=function(n){<br />
$tmp1 = $mod.i;<br />
if ($tmp1 == 1){<br />
} else if ($tmp1 == 2) {<br />
i=3;<br />
} else {<br />
i=4;<br />
}<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating with..do==<br />
<br />
JavaScript has a '''with''', but it is slow and deprecated. Instead a temporary variable is used:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TClassA = class<br />
i: integer;<br />
end;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
begin<br />
with TClassA.Create do<br />
i:=3;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod, "TClassA", pas.System.TObject, function () {<br />
this.$init = function () {<br />
this.i = 0;<br />
};<br />
});<br />
this.DoIt = function(){<br />
var $with1 = $mod.TClassA.$create("Create");<br />
$with1.i = 3;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: If the with-expression is already a local variable no new variable is created. This is Delphi/FPC compatible.<br />
<br />
==Translating enums==<br />
<br />
Enum values are translated to numbers. The enum type is translated to an object containing a mapping from name to number and number to name.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TMyEnum = (<br />
Red,<br />
Green,<br />
Blue);<br />
var<br />
e: TMyEnum = Blue;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
begin<br />
e := Green;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TMyEnum = {<br />
"0":"Red",<br />
Red:0,<br />
"1":"Green",<br />
Green:1,<br />
"2":"Blue",<br />
Blue:2<br />
};<br />
this.e = $mod.TMyEnum.Blue;<br />
this.DoIt = function(){<br />
$mod.e = $mod.TMyEnum.Green;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Supported: ord(), low(), high(), pred(), succ(), type cast number to enum.<br />
* With optimization level -O1 the compiler uses numbers instead of names.<br />
* Not yet implemented: custom values for enum values.<br />
<br />
<br />
<br />
==Translating sets==<br />
<br />
A set s is translated to a JavaScript object, where for each included enum holds ''s.enumvalue==true''. This allows arbitrary large sets and the ''in'' operator is fast.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TColor = (Red, Green, Blue);<br />
TColors = set of TColor;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
var<br />
c: TColor;<br />
S, T: TColors;<br />
b: boolean;<br />
begin<br />
S:=T;<br />
b:=Red in S;<br />
Include(S,Blue);<br />
Exclude(S,Blue);<br />
S:=S+T;<br />
S:=S-[Red,c];<br />
b:=c in [Red..Blue];<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TColor = {<br />
"0":"Red",<br />
Red:0,<br />
"1":"Green",<br />
Green:1,<br />
"2":"Blue",<br />
Blue:2<br />
};<br />
$mod.DoIt = function(){<br />
var c = 0;<br />
var S = {};<br />
var T = {};<br />
var b = false;<br />
S = rtl.refSet(T);<br />
b = $mod.TColor.Red in S;<br />
S = rtl.includeSet(S,$mod.TColor.Blue);<br />
S = rtl.excludeSet(S,$mod.TColor.Blue);<br />
S = rtl.unionSet(S,T);<br />
S = rtl.diffSet(S,rtl.createSet($mod.TColor.Red,c));<br />
b = c in rtl.createSet(null,$mod.TColor.Red,$mod.TColor.Blue);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Supported:<br />
** Include<br />
** Exclude<br />
** literal<br />
** literal range, e.g. ''[EnumA..EnumB], ['a'..'z']''<br />
** union +<br />
** difference -<br />
** intersect *<br />
** symmetrical difference &gt;&lt;<br />
** equal =<br />
** unequal &lt;&gt;<br />
** subset &lt;=<br />
** superset &gt;=<br />
** set of anonymous enum type: ''set of (enum1,enum2,...)''<br />
* Not supported: set of char, set of boolean<br />
* There is no optimization yet for small sets like in Delphi/FPC.<br />
* Assigning a set or passing the set as an argument only creates a reference and marks the set as ''shared''. When a ''shared'' set is altered with Include/Exclude a new set is created (copy on write).<br />
* Passing a set as an argument might clone the set. Use the ''const'' modifier for parameters whenever possible.<br />
* Constant sets in expressions (e.g. ''if c in ['a'..'z'] then'') are not yet optimized and created every time. Create a ''const'' to avoid this.<br />
<br />
<br />
<br />
==Translating array type==<br />
<br />
All arrays are translated into JavaScript arrays.<br /> Contrary to Delphi/FPC dynamic arrays are not reference counted and do not copy on write. That means if you pass an array to a procedure and change an element, the original array is changed.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TIntArr = Array of integer;<br />
TObjArr = Array of TObject;<br />
TRec = record c: char; end;<br />
TRecArr = Array of TRec;<br />
Procedure Test;<br />
Implementation<br />
Procedure Test;<br />
Var<br />
IntArr: TIntArr = (1,2,3);<br />
ObjArr: TObjArr;<br />
RecArr: TRecArr;<br />
Begin<br />
IntArr:=nil;<br />
SetLength(IntArr,4);<br />
IntArr[2]:=2;<br />
IntArr[1]:=length(IntArr);<br />
SetLength(ObjArr,5);<br />
SetLength(RecArr,2,TRec);<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.Test = function(){<br />
this.TRec = function(s){<br />
if (s){<br />
this.c = s.c;<br />
} else {<br />
this.c = "";<br />
};<br />
this.$equal = function(b){<br />
return (this.c == b.c);<br />
};<br />
};<br />
this.IntArr = [1,2,3];<br />
this.ObjArr = [];<br />
this.RecArr = [];<br />
this.Test = function(){<br />
$mod.IntArr = [];<br />
rtl.arraySetLength($mod.IntArr,4,0);<br />
$mod.IntArr[2] = 2;<br />
$mod.IntArr[1] = $mod.IntArr.length;<br />
rtl.setArrayLength($mod.ObjArr,5,null);<br />
rtl.setArrayLength($mod.RecArr,2,$mod.TRec);<br />
}<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Supported features of dynamic arrays: SetLength(), Length(), equal/notequal nil, low(), high(), assigned(), concat(), copy(), insert(), delete(), multi dimensional, array of record<br />
* Dynamic array constants. E.g. in mode ObjFPC ''const a: array of byte = (1,2)''. In mode Delphi you must use square brackets, ''... = [1,2]''<br />
* Supported features of static arrays: length(), low(), high(), assigned(), concat(), copy(), const, const records<br />
* Open arrays are implemented as dynamic arrays.<br />
* Calling ''Concat()'' with only one array simply returns the array (no cloning). Calling it with multiple arrays creates a clone. This is Delphi 10.1 compatible.<br />
* In Delphi/FPC an empty array is ''nil''. In JS it can be ''null'' or ''[]''. For compatibility comparing an array with ''nil'' checks for ''length(a)&gt;0''.<br />
* ''function Assigned(array): boolean'' results true iff ''length(array)&gt;0''.<br />
* array of const:<br />
** Works the same: vtInteger, vtBoolean, vtPointer, vtObject, vtClass, vtWideChar, vtInterface, vtUnicodeString<br />
** <nowiki>''longword'' is converted to ''vtNativeInt''. Delphi/FPC converts to ''vtInteger'', changing big numbers to negative numbers.</nowiki><br />
** vtExtended is double, Delphi/FPC: PExtended<br />
** vtCurrency is currency, Delphi/FPC: PCurrency<br />
** Not supported: vtChar, vtString, vtPChar, vtPWideChar, vtAnsiString, vtVariant, vtWideString, vtInt64, vtQWord<br />
** only in pas2js: vtNativeInt, vtJSValue<br />
* Assignation using constant array, e.g. ''a:=[1,1,2];''<br />
* String like operation: + operator concatenates arrays. e.g. ''a:=[1]+[2];''. This is controlled by modeswitch arrayoperators, which is enabled in mode delphi.<br />
* ''function copy(array,start=0,count=max): array''<br />
* ''procedure insert(item,var array,const position)''<br />
* ''procedure delete(var array,const start,count)''<br />
<br />
<br />
<br />
==Translating class type==<br />
<br />
Classes are implemented using ''Object.create'' and some rtl magic.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TClassA = Class(TObject)<br />
Public<br />
i: integer;<br />
Procedure Add(a: integer);<br />
End;<br />
var<br />
ObjA: TClassA;<br />
Implementation<br />
Procedure TClassA.Add(a: integer);<br />
Begin<br />
i:=i+a;<br />
End;<br />
Initialization<br />
ObjA:=TClassA.Create;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TClassA",pas.System.TObject,function(){<br />
this.$init = function () {<br />
this.i = 0;<br />
};<br />
this.Add = function(a){<br />
this.i = this.i + a;<br />
};<br />
});<br />
this.ObjA = null;<br />
$mod.$init = function(){<br />
$mod.ObjA = $mod.TClassA.$create("Create");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Each class and each instance is an JS object.<br />
* Each class has a globally unique JS object, created by rtl.createClass.<br />
* ''Self'' is never ''nil''.<br />
* The method ''TObject.Free'' is using compiler magic. See [#tobjectfree Translating TObject.Free].<br />
* ''Class.$class'' is a reference to the class itself.<br />
* ''Class.$ancestor'' is a reference to the ancestor class.<br />
* A class has ''c.$ancestor == Object.getPrototypeOf(c)''.<br />
* A class instance has ''o.$class == Object.getPrototypeOf(o)''.<br />
* ''Class.$classname'' is the short name. E.g. ''TClassA.$classname == 'TClassA'''.<br />
* ''Class.$name'' is the long name. E.g. ''TClassA.$name == 'MyModule.TClassA'''.<br />
* ''Class.$unitname'' is the unit name. E.g. ''TClassA.$unitname == 'MyModule'''.<br />
* The "''is''"-operator is implemented using "''isPrototypeOf''". Note that "''instanceof''" cannot be used, because classes are JS objects.<br />
* The "''as''" operator is implemented as ''rtl.as(Object,Class)''.<br />
* Supported:<br />
** constructor, destructor<br />
** private, protected, public, strict private, strict protected<br />
** class vars, const, nested types<br />
** methods, class methods, class constructor, external methods<br />
** method modifiers overload, reintroduce, virtual, override, abstract, static, external name, message integer, message string<br />
** call inherited<br />
** assigned()<br />
** type cast<br />
** class sealed, class abstract<br />
* Not supported: class destructor<br />
* Property:<br />
** References are replaced by getter/setter.<br />
** Supported: argument lists, default property, class property, stored modifier, index modifier.<br />
** Not supported: getter/setter to an array element, e.g. ''property A: char read FArray[0];''<br />
** Class property getter/setter can be static or non static. Delphi: must be static.<br />
** The ''Index'' modifier supports any constant, e.g. a string, while Delphi only allows an ordinal (longint). -2147483648 is not a special number in pas2js. Overriding a property with an index property is allowed in Delphi and pas2js.<br />
<br />
<br />
<br />
==Translating class-of type==<br />
<br />
A class-of is a reference to a class. See above about translating class.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TBird = Class(TObject)<br />
Public<br />
Class var Count: integer;<br />
Class Procedure Add(a: integer); virtual;<br />
End;<br />
TBirds = class of TBird;<br />
<br />
TPigeon = Class(TBird)<br />
Public<br />
Class Procedure Add(a: integer); override;<br />
End;<br />
<br />
var<br />
BirdType: TBirds;<br />
Implementation<br />
Class Procedure TBird.Add(a: integer);<br />
Begin<br />
Count:=Count+a;<br />
End;<br />
Class Procedure TPigeon.Add(a: integer);<br />
Begin<br />
inherited Add(a+1);<br />
End;<br />
Initialization<br />
BirdType:=TPigeon;<br />
BirdType.Add(1);<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TBird",pas.System.TObject,function () {<br />
this.Count = 0;<br />
this.Add = function (a) {<br />
this.Count = this.Count + a;<br />
};<br />
});<br />
rtl.createClass($mod,"TPigeon",$mod.TBird,function () {<br />
this.Add = function (a) {<br />
$mod.TBird.Add.call(this,a + 1);<br />
};<br />
});<br />
$mod.$init = function(){<br />
$mod.BirdType = $mod.TPigeon;<br />
$mod.BirdType.Add(1);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note that ''this'' in a class method is the class itself.<br /><br /> Notes:<br /><br />
<br />
* Contrary to Delphi/FPC the "is" operator works with class-of.<br />
<br />
<br />
<br />
==Translating TObject.Free==<br />
<br />
In Delphi/FPC AnObject.Free checks if Self is nil, then calls the destructor and frees the memory, without changing the reference. In JavaScript however calling a method with AnObject=nil causes a crash. And memory cannot be freed explicitely. Memory is only freed if all references are gone (e.g. set to ''null'').<br /> Therefore pas2js adds code to call the destructor and sets the variable to ''nil'':<br /><br />
<br />
* ''Obj.Free'' on a local variable or argument is translated to ''Obj = rtl.freeLoc(Obj);''.<br />
* ''Obj.Free'' on a non local variable is translated to ''rtl.free(this,"Obj");''.<br />
* Not supported: Freeing a property or function result.<br /> For example ''List[i].Free'' gives a compiler error. The property setter might create side effects, which would be incompatible to Delphi/FPC.<br />
<br />
Notes:<br />
<br />
* If the destructor raises an exception, the variable is not set to ''nil''. This is compatible to Delphi/FPC, where the memory is not freed in this case.<br />
* Alternatively you can use ''FreeAndNil'', which first changes the variable to ''nil'' and then calls the destructor.<br />
<br />
<br />
<br />
==Translating class interfaces==<br />
<br />
JavaScript has nothing like it, so they are emulated.<br /> An interfacetype is a JS-object with some hidden properties, containing the GUID ($guid) and an array with the method names ($names). Here is how IUnknown looks like in JS:<br /><br />
<br />
<br />
{<br />
$module: [object Object],<br />
$name: "IUnknown",<br />
$fullname: "System.IUnknown",<br />
$guid: "{00000000-0000-0000-C000-000000000046}",<br />
$names: ["QueryInterface","_AddRef","_Release"],<br />
$rtti: [object Object],<br />
$kind: "com",<br />
}<br />
<br />
A class implementing interfaces has a variable ''$intfmaps'', which has for each implemented GUID a map or delegator function. A map is a JS instance of the interfacetype plus a for each method name a function to call the class method. Here is an example map of ''IUnknown'' of ''TInterfacedObject'':<br /><br />
<br />
<br />
{<br />
QueryInterface: function (){ return fn.apply(this.$o,arguments); },<br />
_AddRef: function (){ return fn.apply(this.$o,arguments); },<br />
_Release: function (){ return fn.apply(this.$o,arguments); },<br />
...<br />
}<br />
<br />
When an interface is created for an object (here: a Pascal class instance), for example by using the ''as''-operator "''ObjVar as IUnknown''", a JS object is created, which is an instance of the map object with its ''$o'' set to the ''ObjVar''.<br /><br /> Supported:<br />
<br />
* methods, properties, default property<br />
* ''{$interfaces com|corba|default}''<br /><br />
** COM is default, default ancestor is IUnknown (mode delphi: IInterface), managed type, i.e. automatically reference counted via _AddRef, _Release, the checks for support call QueryInterface<br />
** CORBA: lightweight, no automatic reference counting, no default ancestor, fast support checks.<br />
* inheriting<br />
* An interface without a GUID gets one autogenerated from its name and method names.<br />
* Contrary to Delphi/FPC you can assign an interface type or var to the type TGuidString.<br />
* a class implementing an interface must not be external<br />
* a ClassType "supports" an interface, if it itself or one of its ancestors implements the interface. It does not automatically support an ancestor of the interface.<br />
* method resolution, procedure IUnknown._AddRef = IncRef;<br />
* delegation: property Name: interface|class read Field|Getter implements AnInterface;<br />
* is-operator:<br />
** IntfVar is IntfType - types must be releated<br />
** IntfVar is ClassType - types can be unrelated, class must not be external<br />
** ObjVar is IntfType - can be unrelated<br />
* as-operator<br />
** IntfVar as IntfType - types must be releated<br />
** IntfVar as ClassType - types can be unrelated, nil returns nil, invalid raises EInvalidCast<br />
** ObjVar as IntfType - can be unrelated, nil if not found, COM: uses _AddRef<br />
* typecast:<br />
** IntfType(IntfVar) - must be related<br />
** ClassType(IntfVar) - can be unrelated, nil if invalid<br />
** IntfType(ObjVar) - nil if not found, COM: if ObjVar has delegate uses _AddRef<br />
** TJSObject(IntfTypeOrVar). Note that you cannot typecast directly to a ''TJSObject'' descendant. You can use ''TJSWindow(TJSObject(IntfType))''.<br />
** jsvalue(intfvar)<br />
* Assign operator:<br />
** IntfVar:=nil;<br />
** IntfVar:=IntfVar2; - IntfVar2 must be same type or a descendant<br />
** IntfVar:=ObjVar; - nil if unsupported<br />
** jsvalue:=IntfVar;<br />
** TGUIDVar:=IntfType;<br />
** TGUIDVar:=IntfVar;<br />
** TGUIDVar:=stringconstant;<br />
** TGUIDStringVar:=IntfVar;<br />
** StringVar:=GuidVar;<br />
* Equal/Inequal operator:<br />
** IntfVar=nil;<br />
** IntfVar=IntfVar2; - must be related<br />
** jsvalue=IntfVar;<br />
** TGUIDVar=IntfType;<br />
** TGUIDVar=IntfVar;<br />
** TGUIDVar=string;<br />
** TGUIDStringVar=IntfVar;<br />
* Passing an COMIntfVar to an untyped parameter or ''JSValue'' does not trigger ''_AddRef, _Release''.<br />
* Assigned(IntfVar)<br />
* RTTI, typeinfo(IntfType), typeinfo(IntfVar)<br />
<br />
Not yet supported: array of intferfacetype, interface as record member.<br />
<br />
==Translating helpers==<br />
<br />
Pas2js supports class helpers, record helpers and type helpers since 1.3. The extend is only virtual, the helped type is kept untouched. <br /><br />
<br />
* A '''class helper''' can "extend" Pascal classes and external JS classes.<br />
* A '''record helper''' can "extend" a record type. In $mode delphi a record helper can extend other types as well, see ''type helper''<br />
* A '''type helper''' can extend all base types like integer, string, char, boolean, double, currency, and user types like enumeration, set, range, array, class, record and interface types. It cannot extend helpers and procedural types.<br /> Type helpers are enabled by default in ''$mode delphi'' and disabled in ''$mode objfpc''. You can enable them with '''{$modeswitch typehelpers}'''.<br />
* By default only one helper is active per type, same as in FPC/Delphi. If there are multiple helpers for the same type, the last helper in scope wins.<br /> A class with ancestors can have one active helper per ancestor type, so multiple helpers can be active, same as FPC/Delphi.<br /> Using '''{$modeswitch multihelpers}''' you can activate all helpers within scope.<br />
* Nested helpers (e.g. ''TDemo.TSub.THelper'') are elevated. Visibility is ignored. Same as FPC/Delphi.<br />
* Helpers cannot be forward defined (e.g. no ''THelper = helper;'').<br />
* Helpers must not have fields.<br />
* '''Class Var, Const, Type'''<br />
* '''Visibility''' : ''strict private .. published''<br />
* '''Function, procedure''': In class and record helpers ''Self'' is the class/record instance. For other types Self is a reference to the passed value.<br />
* '''Class function, class procedure''': Helpers for Pascal classes/records can add ''static'' and non static class functions. Helpers for external classes and other types can only add static class functions.<br />
* '''Constructor'''. Not for external classes. Works similar to construcors, i.e. ''THelpedClass.Create'' creates a new instance, while ''AnObj.Create'' calls the constructor function as normal method. Note that Delphi does not allow calling helper construcors as normal method.<br />
* no destructor<br />
* '''Property''' : getters/setters can refer to members of the helper, its ancestors and the helped class/record.<br />
* '''Class property''' : getter can be static or non static. Delphi/FPC only allows static.<br />
* '''Ancestors''' : Helpers can have an ancestor helper, but they do not have a shared root class, especially not ''TObject''.<br />
* '''no virtual, abstract, override'''. Delphi allows them, but 10.3 crashes when calling.<br />
* '''inherited''' : ''inherited'' inside a method of a class/record calls helper of ancestor.<br />''inherited'' inside a helper depends on the $mode:<br />
** ''$mode objfpc'' : ''inherited;'' and ''inherited Name(args);'' work the same and searches first in HelperForType, then in ancestor(s).<br />
** ''$mode delphi: inherited;'' : skip ancestors and HelperForType, searches first in helper(s) of ancestor of HelperForType.<br />
** ''$mode delphi: inherited name(args);'' : same as $mode objfpc first searches in HelperForType, then Ancestor(s) In any case if ''inherited;'' has no ancestor to call, it is silently ignored, while ''inherited Name;'' gives an error.<br />
* '''RTTI''': ''typeinfo(somehelper)'' returns a pointer to ''TTypeInfoHelper'' with ''Kind tkHelper''.<br />
* There are some special cases when using a '''type helper''' function/procedure on a value:<br />
** ''function result'' : using a temporary variable<br />
** ''const, const argument'' : When helper function tries to assign a value, pas2js raises a EPropReadOnly exception. FPC/Delphi use a temporary variable allowing the write.<br />
** ''property'' : uses only the getter, ignoring the setter. This breaks OOP, as it allows to change fields without calling the setter. This is FPC/Delphi compatible.<br />
** ''with value do ;'' : uses a temporary variable. Delphi/FPC do not support it.<br />
* A method with ''external name'' modifier is treated as an external method of the helped type.<br />
<br />
<br />
<br />
==Translating attributes==<br />
<br />
Attributes are stored in the TTypeInfo objects as streams stored in an array. See the function ''GetRTTIAttributes'' in unit ''TypInfo'' for details.<br />
<br />
==Translating try..finally==<br />
<br />
JavaScript has the same, so it translates straight forward.<br />
<br />
==Translating try..except==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses SysUtils, Math, JS;<br />
Function DoIt(n: integer): double;<br />
Implementation<br />
Function DoIt(n: integer): double;<br />
var E: Exception;<br />
Begin<br />
try<br />
Result:=double(7.0)/n;<br />
if not IsFinite(Result) then<br />
if n=0 then<br />
raise EZeroDivide.Create<br />
else<br />
raise EOverflow.Create;<br />
except<br />
on EZeroDivide do Result:=0.0;<br />
on E2: EOverflow do Result:=0.0;<br />
else<br />
raise EAbort.Create('Something other: '+String(JS.JSExceptObject));<br />
end;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","SysUtils"],<br />
function(){<br />
this.DoIt=function(n){<br />
Result = 0;<br />
var E = null;<br />
try{<br />
Result = 7.0 / n;<br />
if (!IsFinite(Result)){<br />
if (n==0){<br />
throw pas.SysUtils.EZeroDivide.$create("Create");<br />
} else {<br />
throw pas.SysUtils.EOverflow.$create("Create");<br />
};<br />
};<br />
}catch($e){<br />
if (pas.SysUtils.EZeroDivide.isPrototypeOf($e)){<br />
Result = 0.0;<br />
} else if (pas.SysUtils.EOverflow.isPrototypeOf($e)){<br />
var E2 = $e;<br />
Result = 0.0;<br />
} else {<br />
throw pas.SysUtils.EAbort.$create("Create",["Something other: "+(""+$e)]);<br />
}<br />
}<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Division by zero does not raise an exception in JavaScript. Instead it results in Infinity, except for 0/0 which results in NaN.<br />
* There is no ExceptObject in SysUtils.<br />
* When calling external functions keep in mind that JS allows to throw (raise) any value, often a string.<br /> You can access the current except value via JSExceptValue in unit JS.<br /> Note that this is only valid inside the catch-block. The compiler will not warn, if you use it outside.<br />
<br />
<br />
<br />
==Translating enumerators==<br />
<br />
The for..in..do supports enumerating:<br />
<br />
* ordinal types like char, boolean, byte, ..., longword, enums, custom ranges are translated to a for loop.<br />
* set types are translated to a for loop, while const sets and set variables are enumerated via a for(...in...) loop.<br />
* string and array variables are enumerated via for loops.<br />
* for aString in ArrayOfString do ...<br />
* ''for key in jsvalue do'' translates to ''for (key in jsvalue){}''<br />
* ''for key in ExternalClass do''<br /><br />
** <nowiki>If the externalclass has a ''length'' and a matching default property it uses the enumeration of an array. For example </nowiki>''for value in TJSArray do'' enumerates the values of the array, not the index. It checks if the array is nil.<br />
** Otherwise it translates to ''for (key in externalclass){}'', which enumerates the keys (property names) of the JS object.<br />
<br />
The class GetEnumerator function is translated like this:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
<br />
uses Classes;<br />
<br />
procedure DoIt(List: TList);<br />
<br />
Implementation<br />
<br />
procedure DoIt(List: TList);<br />
var<br />
Item: Pointer;<br />
begin<br />
for Item in List do<br />
if Item&lt;&gt;nil then ;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","Classes"],<br />
function(){<br />
this.DoIt=function(List){<br />
var Item = null;<br />
var $in1 = List;<br />
try {<br />
while ($in1.MoveNext()) {<br />
Item = $in1.GetCurrent();<br />
if (Item !== null) ;<br />
}<br />
} finally {<br />
$in1 = rtl.freeLoc($in1)<br />
};<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Not supported: operator Enumerator, member modifier enumerator (i.e. custom Current and MoveNext)<br />
<br />
<br />
<br />
==Translating function types==<br />
<br />
JavaScript functions work like Delphi's "reference to function", which means like closures, capturing outer variables. Assigning a normal function or nested function to a procedural variable is translated to a simple assignment. A Pascal method needs '''this''' to be the class or class instance.<br /> Note that ''bind'' cannot be used, because it does not support the ''equal'' operator. Instead a wrapper is created:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
type<br />
TMyMethod = procedure(n: integer) of object;<br />
TBird = class<br />
procedure DoIt(n: integer); virtual; abstract;<br />
end;<br />
TMyProc = procedure(n: integer);<br />
procedure DoSome(n: integer);<br />
begin<br />
end;<br />
var<br />
m: TMyMethod;<br />
Bird: TBird;<br />
p: TMyProc;<br />
Begin<br />
m:=@Bird.DoIt;<br />
m(3);<br />
p:=@DoSome;<br />
p(4);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System","UnitA"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TBird",pas.System.TObject,function(){<br />
this.DoIt = function (n) {<br />
};<br />
});<br />
this.DoSome = function (n) {<br />
};<br />
this.m = null;<br />
this.Bird = null;<br />
this.p = null;<br />
$mod.$main = function() {<br />
$mod.m = rtl.createCallback($mod.Bird,"DoIt");<br />
$mod.m(3);<br />
$mod.p = $mod.DoSome;<br />
$mod.p(4);<br />
};<br />
},<br />
[]);<br />
<br />
rtl = {<br />
...<br />
createCallback: function(scope, fn){<br />
var cb;<br />
if (typeof(fn)==='string'){<br />
cb = function(){<br />
return scope[fn].apply(scope,arguments);<br />
};<br />
} else {<br />
cb = function(){<br />
return fn.apply(scope,arguments);<br />
};<br />
};<br />
cb.scope = scope;<br />
cb.fn = fn;<br />
return cb;<br />
},<br />
...<br />
|}<br />
<br />
Notes:<br />
<br />
* You can assign a nested procedure to procedure variable. You don't need and you must not add the FPC "''is nested''" modifier.<br />
* In pas2js a procedural typed declared as '''reference to''' accepts procedures, local procedures and methods. Delphi only supports capturing procedures and methods. FPC 3.0.4 does not support reference-to.<br />
* In pas2js the calling convention ''safecall'' has a special meaning:<br /> Assigning a procedure/method, uses ''rtl.createSafeCallback'' instead of ''createCallback'', enclosing a call in a ''try..catch'' block. When an exception is thrown by JS, it is caught and delegated to ''rtl.handleUncaughtException(err)''.<br /> For example:<br />''aButtonElement.OnClick:=@DoClick;'' uses ''rtl.createSafeCallback''<br />''aButtonElement.OnClick:=SomeElement.OnClick;'' does not.<br /><br />
<br />
<br />
<br />
==Translating anonymous functions==<br />
<br />
Anonymous functions are supported since pas2js 1.1.<br /> Note that in pas2js local procedures are closures as well. See below.<br /> For pas2js 1.0 the next best thing are local procedures. For example:<br />
<br />
{| class="sample"<br />
! Delphi<br />
! Pas2js<br />
|-<br />
|<br />
Program MyModule;<br />
type<br />
TAdder = reference to function(n: integer): integer;<br />
<br />
function CreateAdder(a: integer): TAdder;<br />
begin<br />
Result:=function(b: integer)<br />
begin<br />
Result:=a+b;<br />
end;<br />
end;<br />
<br />
var<br />
Adder: TAdder;<br />
Begin<br />
Adder:=CreateAdder(3);<br />
writeln(Adder(5)); // gives 8<br />
End.<br />
|<br />
Program MyModule;<br />
type<br />
TAdder = reference to function(n: integer): integer;<br />
<br />
function CreateAdder(a: integer): TAdder;<br />
function Add(b: integer): integer;<br />
begin<br />
Result:=a+b;<br />
end;<br />
begin<br />
Result:=@Add;<br />
end;<br />
<br />
var<br />
Adder: TAdder;<br />
Begin<br />
Adder:=CreateAdder(3);<br />
writeln(Adder(5)); // gives 8<br />
End.<br />
|}<br />
<br />
<br />
<br />
==Translating var modifier absolute==<br />
<br />
The absolute modifier works as an alias. That means it works FPC/Delphi compatible for related types like Pointer and TObject, and works incompatible for unrelated types like longword and record (e.g. ''var r: TPoint absolute MyLongInt'').<br /> The modifier is currently only supported for local variables.<br />
<br />
==Translating assert()==<br />
<br />
The Assert(boolean[,string]) function is translated to ''if(bool) throw x''. If unit sysutils is used, it creates an EAssertFailed exception.<br /> Otherwise it throws a string.<br /><br />
<br />
* Command line enable with -Sa, disable with -Sa-<br />
* In code enable with ''{$C+}'' or ''{$Assertions on}'', disable with ''{$C-}'' or ''{$Assertions off}''<br />
<br />
<br />
<br />
==Dispatch messages==<br />
<br />
The procedure modifier '''message''' and the '''Dispatch''' method works similar to FPC/Delphi, as it expects a record of a specific format and '''''TObject.Dispatch''''' calls the corresponding method with that message number or string.<br /> The procedure modifier ''message &lt;integer&gt;'' adds an entry to hidden ''YourClass.$msgint'' object, while the modifier ''message &lt;string&gt;'' adds an entry to the hidden ''YourClass.$msgstr'' object.<br /> Two new directives '''''{$DispatchField fieldname}''''' and '''''{$DispatchStrField fieldname}''''' were added. Insert these directives in front of your class declaration to let the compiler check all methods with message modifiers of this class and its descendants whether they pass a record with the required field. For example:<br />
<br />
<br />
{$DispatchField Msg} // enable checking message methods for record field name "Msg"<br />
{$DispatchStrField MsgStr}<br />
TObject = class<br />
procedure Dispatch(var aMessage); virtual;<br />
procedure DispatchStr(var aMessage); virtual;<br />
end;<br />
TMouseDownMsg = record<br />
Id: integer; // Id instead of Msg, works in FPC, but not in pas2js<br />
x,y: integer;<br />
end;<br />
TMouseUpMsg = record<br />
MsgStr: string;<br />
X,Y: integer;<br />
end;<br />
TWinControl = class<br />
procedure MouseDownMsg(var Msg: TMouseDownMsg); message 3; // warning: Dispatch requires record field Msg<br />
procedure MouseUpMsg(var Msg: TMouseUpMsg); message 'up'; // ok, record with string field name MsgStr<br />
end;<br />
<br />
Note that descendant classes can override the ''$DispatchField'' or disable the check using ''{$DispatchField -}''.<br />
<br />
==Calling JavaScript from Pascal==<br />
<br />
Pas2js allows to write low level functions and/or access a JavaScript library with the following possibilities:<br />
<br />
==The asm block==<br />
<br />
The asm block is pure JavaScript, that is copied directly into the generated .js file.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
var<br />
s: string;<br />
Begin<br />
s = 'Hello World!';<br />
Asm<br />
console.log(s);<br />
End;<br />
End.<br />
|<br />
<nowiki>rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.s = '';<br />
$mod.$main = function(){<br />
$mod.s = "Hello World!";<br />
console.log(s);<br />
};<br />
},<br />
[]);<br />
</nowiki><br />
|}<br />
<br />
Notes:<br />
<br />
* The block is indented to produce more readable JS code. All lines are indented or unindented the same amount, i.e. sub indentation is kept.<br />
* The compiler does neither parse, nor check the syntax of the JS.<br />
* The compiler does not know what Pascal identifiers are used by the asm-block and might remove them, if no Pascal code is using them. To make sure that an identifier is kept, add some dummy code like ''if MyVar=0 then;''<br />
* Accessing an interface, program or library identifier:<br /><br />
** From inside the module you can use ''$mod.Identifier''.<br />
** Otherwise use the fully qualified path ''pas.Unitname.Identifier''.<br />
* Accessing an implementation identifier:<br /><br />
** From inside the unit you can use ''$impl.Identifier''.<br />
** Otherwise use the path ''pas.Unitname.$impl.Identifier''.<br />
* Accessing a class instance member (field, procedure, function, constructor, destructor) from a method of the class: use ''this.Identifier''. Inside a nested function of a method you use the ''Self.Identifier''.<br />
* Accessing a class member (class var, class procedure, class function) from a method of the class: for writing use ''this.$class.Identifier'', for reading you can omit the ''$class''.<br />
* Accessing a class member (class var, class procedure, class function) from a class method of the class: use ''this.Identifier''.<br />
* Access to Properties must use the getter/setter.<br />
* When calling a Pascal method, make sure the '''this''' is correct:<br />
** A class method (e.g. ''class function'', ''class procedure'') needs the class as ''this''.<br />'''Wrong''': ''aCar.DoIt(params,...)''<br />'''Correct''': ''aCar.$class.DoIt(params,...)''<br /><br />
* Calling a Pascal function from a HTML/DOM-element: For example to call a function when user clicks a DOM element you can assign a function to the ''onclick'' property. This will call the function with ''this'' set to the DOM element.<br /> Pascal methods needs a wrapper to set ''this'' to the instance. Examples:<br />
** An unit function: ''DOMElement.onclick = $mod.DoIt;''<br />
** An implementation function: ''DOMElement.onclick = $impl.DoIt;''.<br />
** A method: ''DOMElement.onclick = this.DoIt.bind(this);''<br />
** A class function/procedure: ''DOMElement.onclick = this.DoIt.bind(this.$class);''<br />
** A nested function: ''DOMElement.onclick = DoIt;''.<br />
<br />
<br />
<br />
==The procedure modifier assembler==<br />
<br />
You can write pure JavaScript functions like this:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
<br />
Procedure Log(const s: string); assembler;<br />
Asm<br />
console.log(s);<br />
end;<br />
<br />
Begin<br />
Log('Hello World!');<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.Log = function(s){<br />
console.log(s);<br />
};<br />
$mod.$main = function(){<br />
$mod.Log("Hello World!");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
See also [#asm asm].<br />
<br />
==The procedure modifier external==<br />
<br />
The procedure modifier ''external'' requires a string constant and tells the compiler to replace a reference with this string value. The value is not checked for JS syntax.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure ConsoleLog(const s: string); external name 'console.log';<br />
// Note: an external procedure has no begin..end block<br />
Begin<br />
ConsoleLog('Hello World!');<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
$mod.$main = function(){<br />
console.log("Hello World!");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==The procedure modifier varargs==<br />
<br />
Appending the '''varargs''' modifier to a procedure allows to pass arbitrary more parameters to a function. By default these parameters are untyped, i.e. any type fits. Alternatively you can use '''varargs of aType''' to allow only specific types.<br /> To access these arguments use either ''JSArguments'' from unit JS or an ''asm..end'' block.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
uses JS;<br />
function Sum(b: boolean): longint; varargs;<br />
var i: longint;<br />
begin<br />
if b then<br />
asm<br />
for (var i=0; i&lt;arguments.length; i++) Result+=arguments[i];<br />
end<br />
else<br />
for i:=0 to JSArguments.length-1 do<br />
Result:=Result+longint(JSArguments[i]);<br />
end;<br />
var<br />
i: integer;<br />
Begin<br />
i:=Sum(true,2,4,6); // i=12<br />
i:=Sum(false,2,4,6); // i=12<br />
End.<br />
|<br />
rtl.module("program",<br />
["System","JS"],<br />
function(){<br />
var $mod = this;<br />
this.Sum = function(b){<br />
var Result = 0;<br />
var i = 0;<br />
if (b){<br />
for (var i=0; i&lt;arguments.length; i++) Result+=arguments[i];<br />
} else {<br />
for (var $l1 = 1, $le2 = argumens.length; $l1 &lt;= $le2; $l1++){<br />
$i = $l1;<br />
Result = Result + arguments[i];<br />
}<br />
}<br />
return Result;<br />
};<br />
this.i = 0;<br />
$mod.$main = function(){<br />
$mod.i = $mod.Sum(true,2,4,6);<br />
$mod.i = $mod.Sum(false,2,4,6);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
The above example defines a function ''Sum'', that requires the first parameter to be a boolean and then an arbitrary number of parameters. The compiler does not type check the other parameters, so you can pass anything readable.<br />
<br />
==The var modifier external==<br />
<br />
The var modifier ''external'' allows to use a JavaScript variable or constant.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
var<br />
EulersNumber: Double; external name 'Math.E';<br />
d: double;<br />
Begin<br />
d:=EulersNumber;<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.d = 0.0;<br />
$mod.$main = function(){<br />
$mod.d = Math.E;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==The external modifier of class members==<br />
<br />
The method modifier ''external'' works as the procedure modifier, except it uses the scope of the class or instance.<br /> The field modifier ''external'' works as the var modifier, except it uses the scope of the class or instance.<br /> Requires the modeswitch '''externalclass'''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TWrapper = class<br />
private<br />
// let's assume this object has the properties "$Handle", "$id", and "0"<br />
public<br />
Id: NativeInt; external name '$Id';<br />
x: NativeInt; external name '[0]';<br />
y: NativeInt; external name '["A B"]';<br />
function GetState(typ: longint): NativeInt; external name '$Handle.GetState';<br />
procedure DoIt;<br />
end;<br />
procedure TWrapper.DoIt;<br />
begin<br />
Id := GetState(4);<br />
end;<br />
var<br />
W: TWrapper;<br />
Begin<br />
W.Id := 2;<br />
W.x := 3;<br />
W.y := 4;<br />
W.GetState(5);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod, "TWrapper", pas.System.TObject, function () {<br />
this.DoIt = function(){<br />
this.$Id = this.$Handle.GetState(4);<br />
};<br />
});<br />
this.W = null;<br />
$mod.$main = function(){<br />
$mod.W.$Id = 2;<br />
$mod.W[0] = 3;<br />
$mod.W["A B"] = 4;<br />
$mod.W.$Handle.GetState(5);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Non identifiers like "0" or "A B" must be enclosed in brackets.<br />
<br />
<br />
<br />
==External classes==<br />
<br />
pas2js introduces a new class modifier "''external name''", which makes the whole class external. External classes allow to easily declare Pascal wrappers for JavaScript objects and function objects.<br /> They need the modeswitch '''externalclass''' in front of the class.<br /> An external class is not a TObject and has none of its methods.<br /> All members are external. If you omit the ''external'' modifier the external name is the member name. Keep in mind that JS is case sensitive.<br /> Properties work the same as with Pascal classes, i.e. are replaced by Getter/Setter.<br /> Destructors are not allowed.<br /> Constructors are supported in four ways:<br />
<br />
* ''constructor New'' is translated to ''new ExtClass(params)'', and for nested external class: ''new ExtParentClass.ExtClass(Params)''<br />
* ''<nowiki>constructor New; external name ''GlobalFunc''</nowiki>'' is translated to ''new GlobalFunc(params)''.<br />
* ''constructor SomeName; external name '''{}' is translated to ''{}''.<br />
* Otherwise it is translated to ''new ExtClass.FuncName(params)'', and for nested external class: ''new ExtParentClass.ExtClass.FuncName(params)''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TJSDate = class external name 'Date'<br />
private<br />
function getYear: NativeInt;<br />
procedure setYear(const AValue: NativeInt);<br />
public<br />
constructor New;<br />
constructor New(const MilliSecsSince1970: NativeInt);<br />
class function now: NativeInt;<br />
property Year: NativeInt read getYear write setYear;<br />
end;<br />
var<br />
d: TJSDate;<br />
Begin<br />
d:=TJSDate.New;<br />
d.Year:=d.Year+1;<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
this.d = null;<br />
$mod.$main = function () {<br />
$mod.d = new Date();<br />
$mod.d.setYear($mod.d.getYear() + 1);<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* An external class can descend from another external class.<br />
* Any class instance can be type casted to any root class.<br />
* A Pascal class can descend from an external class.<br />
* You can define a class-of external class and the '''is''' and '''as''' operators work similar.<br />
* Class variables work as in JavaScript. That means, each descendant and each instance can have its own value. For example ''TExtA.Value'' might be different from ''InstanceExtA.Value''. Setting ''InstanceExtA.Value'' does not change ''TExtA.Value''.<br />
* Const with an expression are replaced by the expression.<br />
* Const without an expression are treated as a readonly variable.<br />
* Class functions and class procedures are allowed, but can only be called via the class, not via an instance.<br /> For example you can call the class function ''TJSString.fromCharCode()'', but you cannot call ''aJSString.fromCharCode()''.<br />
* Since class types are JS objects it is possible to typecast a class type to the JS Object, e.g. ''TJSObject(TObject)''. Note that you cannot typecast directly to a ''TJSObject'' descendant in $mode objfpc. You can use ''TJSWindow(TJSObject(ExtClassInstance))''.<br />
* You can typecast function addresses and function references to JS function, e.g. ''TJSFunction(@SomeProc)'', ''TJSFunction(OnClick)''. Keep in mind that typecasting a method address creates a function wrapper to bind the Self argument, except when typecasting to ''TJSFunction'' (pas2js 1.5+).<br />
<br />
==External class as ancestor==<br />
<br />
A Pascal class can descend from an external class - a JS object or function.<br /> The methods ''AfterConstruction'' and ''BeforeDestruction'' are called if they exist.<br /> New instances of a JS Object descendant are created by default with ''Object.create(ancestorclass)''.<br /> New instances of a JS Function descendant are created by default with ''new DescendantFunc()''.<br /> You can override this, by providing a non private <br />'''class function NewInstance(fnname: string; const paramsarray): TPasClass; virtual;'''. This method is called to create a new instance and before calling the constructor. The name is arbitrary, but the function must be the '''first non private''', non external, virtual class function with the class as result type.<br /><br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
<br />
// Example for descending a Pascal class from a JS Object<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TExtA = class external name 'ExtA'<br />
end;<br />
TMyB = class(TExtA)<br />
protected<br />
// optional: override default allocation<br />
class function NewInstance(fnname: string; const paramarray): TMyB; virtual;<br />
end;<br />
class function TMyB.NewInstance(fnname: string; const paramarray): TMyB;<br />
Begin<br />
asm<br />
Result = Object.create(ExtA); // that is what the rtl does<br />
end;<br />
End;<br />
<br />
Begin<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
rtl.createClassExt($mod, "TMyB", ExtA, "NewInstance", function () {<br />
this.$init = function () {<br />
};<br />
this.$final = function () {<br />
};<br />
this.NewInstance = function (fnname, paramarray) {<br />
var Result = null;<br />
Result = Object.create(ExtA);<br />
return Result;<br />
};<br />
});<br />
$mod.$main = function () {<br />
};<br />
});<br />
|}<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
<br />
// Example for descending a Pascal class from a JS Function<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
uses JS;<br />
type<br />
TExternalFunc = class external name 'ExternalFunc'(TJSFunction)<br />
constructor New(a: word);<br />
end;<br />
TMyFunc = class(TExternalFunc)<br />
constructor Create(b: word);<br />
end;<br />
constructor TMyFunc.Create(b: word);<br />
Begin<br />
inherited New(b+1); // optional: call inherited constructor function<br />
End;<br />
<br />
var<br />
f: TMyFunc;<br />
Begin<br />
f:=TMyFunc.Create(3);<br />
writeln(jsInstanceOf(f,TExternalFunc)); // writes true, instanceof operator works as expected<br />
End.<br />
|<br />
rtl.module("program",["System","js"],function () {<br />
var $mod = this;<br />
rtl.createClassExt($mod, "TMyFunc", ExternalFunc, "", function () {<br />
this.$init = function () {<br />
};<br />
this.$final = function () {<br />
};<br />
this.Create$2 = function (b) {<br />
this.$ancestorfunc(b+1);<br />
};<br />
});<br />
this.f = null;<br />
$mod.$main = function () {<br />
f = $mod.TMyFunc.$create("Create$2",[3]);<br />
pas.System.Writeln(pas.JS.jsInstanceOf(f,ExternalFunc));<br />
};<br />
});<br />
|}<br />
<br />
==The JSValue type==<br />
<br />
Pas2js introduces a new type '''JSValue''', which works similar to a JS variable. You can assign almost any value to it and it can be type casted to many types. JSValue is useful for JS wrappers, when a variable can have multiple types. And it can be used for containers storing arbitrary data, e.g. a list of JSValue.<br /> Key features:<br /><br />
<br />
* A JSValue variable initial value is undefined.<br />
* Operators: =, &lt;&gt;<br />
* type casting a ''JSValue'' to ...<br />
** ''Integer: Math.floor(aJSValue)'' Note: may return ''NaN''<br />
** ''Boolean: !(aJSValue == false)'' Note: works for numbers too, ''0==false''<br />
** ''Double: rtl.getNumber(aJSValue)'' Note: ''typeof(n)=="number"?n:NaN;''<br />
** ''String: ""+aJSValue''<br />
** ''Char: rtl.getChar(aJSValue)'' Note: ''((typeof(c)!="string") &amp;&amp; (c.length==1)) ? c : ""''<br />
** class instance or class-of: ''rtl.getObject()'' Note: checks for type ''"object"''<br />
** enum type<br />
** pointer<br />
* A JSValue in a conditional expressions ''If aJSValue then, while aJSValue do, repeat until aJSValue'' has the same meaning as in JS: the condition is true, if the value is not ''<nowiki>undefined, false, null, NaN, 0, ''</nowiki>''. Note that ''new Boolean(false)'' is not ''null'' and the condition is true.<br />
* ''function Assigned(V: jsvalue): boolean'' returns true if<br />''(V!=undefined) &amp;&amp; (V!=null) &amp;&amp; (!rtl.isArray(V) || (V.length &gt; 0))''<br />
* ''function StrictEqual(const A: jsvalue; const B): boolean''<br />
* ''function StrictInequal(const A: jsvalue; const B): boolean''<br />
* Any array can be assigned to an ''array of jsvalue''.<br />
* is-operator: ''jsvalue is class-type'', ''jsvalue is class-of-type''<br /><br />
* The unit JS provides many utility functions for JSValue, like ''hasString, hasValue, isBoolean, isNumber, isInteger, isObject, isClass, isClassInstance, etc..''<br />
<br />
<br />
<br />
==Accessing JS object properties with the bracket accessor==<br />
<br />
Pas2js allows to define index properties that map directly to the JS object properties. For example the default property of TJSObject allows to get and set the properties of an object. For example ''TJSObject(AnObject)['Name']:=Value;''<br /> Another example is the default property of TJSArray, that allows access via integers ''aTJSArray[3]:=Value;''<br /> To define your own bracket accessor define a normal index property and define the getter/setter as ''external name '[]'''.<br /> Here is an example for a read only accessor:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TExtA = class external name 'ExtA'<br />
private<br />
function GetItems(Index: integer): String; external name '[]';<br />
public<br />
property Items[Index: integer]: String read GetItems; default;<br />
end;<br />
var<br />
Obj: TExtA;<br />
s: String;<br />
Begin<br />
... get Obj from somewhere ...<br />
s:=Obj[2];<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
this.Obj = undefined;<br />
this.s = "";<br />
$mod.$main = function () {<br />
$mod.s = Obj[2];<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* A property can have a mix of normal accessor and bracket accessor. For example a bracket accessor as getter and a normal function as setter.<br />
<br />
<br />
<br />
==RTTI - Run Time Type Information==<br />
<br />
The RTTI provides access to the type data of all published properties, fields and methods. The type data provides similar information as Delphi/FPC, but the internals are very different. Delphi/FPC uses pointers, variant records and fake static arrays, which have no equivalent in JS. Instead pas2js uses external classes. For example:<br />
<br />
<br />
TTypeInfo = class external name 'rtl.tTypeInfo'<br />
public<br />
Name: String external name 'name';<br />
Kind: TTypeKind external name 'kind';<br />
end;<br />
TTypeInfoClass = class of TTypeInfo;<br />
<br />
TTypeInfoInteger = class external name 'rtl.tTypeInfoInteger'(TTypeInfo)<br />
public<br />
MinValue: NativeInt external name 'minvalue';<br />
MaxValue: NativeInt external name 'maxvalue';<br />
OrdType : TOrdType external name 'ordtype';<br />
end;<br />
<br />
<br />
The '''typeinfo''' function works on type, var, const and property identifiers. By default it returns a ''pointer''. If the typinfo unit is used it returns the appropiate ''TTypeInfo''. For instance ''typeinfo(integer)'' returns a ''TTypeInfoInteger''.<br />''Typeinfo'' of a ''var'' or ''const'' returns the typeinfo of its type, not of its current runtime value. The exception is a class and class-of instance variable (e.g. ''var o: TObject; ... typeinfo(o)''), which returns the typeinfo of the current runtime value. If ''o'' is ''nil'' it will give a JS error.<br /> Local types (i.e. inside a procedure) do not have typeinfo.<br /> Open array parameters are not yet supported.<br /> Note that FPC ''typeinfo(aClassVar)'' returns the compiletime type, so it works on ''nil''.<br /><br />
<br />
==Async/AWait==<br />
<br />
Pas2js supports the JS operators async and await to simplify the use of Promise. The await operator corresponds to three intrinsic Pas2js functions:<br />
<br />
* ''function await(AsyncFunctionWithResultT()): T;'' // implicit promise, the inner () can be omitted<br />
* ''function await(aType; p: TJSPromise): aType;'' // explicit promise requires the resolved type<br />
* ''function await(aType; j: jsvalue): aType;'' // explicit promise requires the resolved type<br />
<br />
The await function can only be used inside a procedure with the async modifier.<br /> Example for the explicit promise:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
<br />
uses JS, Web;<br />
<br />
function ResolveAfter2Seconds: TJSPromise;<br />
begin<br />
Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)<br />
begin<br />
window.setTimeout(procedure<br />
begin<br />
resolve('resolved');<br />
end,<br />
2000); // wait 2 seconds<br />
end);<br />
end;<br />
<br />
procedure AsyncCall; async;<br />
var s: string;<br />
begin<br />
writeln('calling');<br />
s := await(string,resolveAfter2Seconds()); // does not check if result is really a string<br />
writeln(s); // expected output: 'resolved'<br />
end;<br />
<br />
begin<br />
AsyncCall;<br />
end.<br />
|<br />
rtl.module("program",["System","JS","Web"],function () {<br />
"use strict";<br />
var $mod = this;<br />
this.ResolveAfter2Seconds = function () {<br />
var Result = null;<br />
Result = new Promise(function (resolve, reject) {<br />
window.setTimeout(function () {<br />
resolve("resolved");<br />
},2000);<br />
});<br />
return Result;<br />
};<br />
this.AsyncCall = async function () {<br />
var s = "";<br />
pas.System.Writeln("calling");<br />
s = await $mod.ResolveAfter2Seconds();<br />
pas.System.Writeln(s);<br />
};<br />
$mod.$main = function () {<br />
$mod.AsyncCall();<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* The await function does only compile time checks, no runtime checks.<br />
* Inside an async function/procedure you can pass a ''TJSPromise'' to the ''exit()'' function. For example:<br />''exit(aPromise);''<br />''exit(inherited);''<br />
<br />
<br />
<br />
==Compiler directives==<br />
<br />
In config files:<br />
<br />
* <nowiki>#IFDEF macroname</nowiki><br />
* #IFNDEF macroname<br />
* #IF expression - same as $if, except only defines<br />
* #ELSEIF<br />
* #ELSE<br />
* #ENDIF<br />
* #ERROR text<br />
<br />
In source files:<br />
<br />
* <span class="tt">{$Define ''MacroName''}</span>: defines macro ''MacroName'' with value '1'.<br />
* <span class="tt">{$Define ''MacroName:=value''}</span>: defines macro ''MacroName'' with custom value.<br />
* <span class="tt">{$Undef ''MacroName''}</span>: undefines macro ''MacroName''.<br />
* <span class="tt">{$IfDef ''MacroName''}</span>: if ''MacroName'' is not defined, skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br />
* <span class="tt">{$IfNDef ''MacroName''}</span>: as <span class="tt">$IfDef</span>, except negated.<br />
* <span class="tt">{$If ''boolean expression''}</span>: if ''expression'' evaluates to true (not '0'), skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br /> Supported functions and operators:<br /><br />
** macro - replaced by its value, a simple define has value '1'<br />
** defined(macro) - '1' if defined, '0' otherwise<br />
** undefined(macro) - as <span class="tt">''not defined(macro)''</span><br />
** option(letter) - same as ''<span class="tt">{$IFOpt letter+}</span>''<br />
** <span class="tt">not</span> - first level of precedence<br />
** <span class="tt">*, /, div, mod, and, shl, shr</span> - second level of precedence<br />
** <span class="tt">+, -, or, xor</span> - third level of precedence<br />
** <span class="tt">=, &lt;&gt;, &lt;, &gt;, &lt;=, &gt;=</span> - fourth level of precedence<br />
** If the operands can be converted to numbers they are combined as numbers, otherwise as strings. Not supported functions and operators:<br /><br />
** <span class="tt">defined(Pascal identifier), undefined(Pascal identifier)</span><br />
** <span class="tt">declared(Pascal identifier)</span><br />
** <span class="tt">in</span> operator<br />
* <span class="tt">{$IfOpt ''Letter+,-''}</span>: if ''expression'' evaluates to true (not '0'), skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br />
* <span class="tt">{$Else}</span>: If previous <span class="tt">$IfDef</span>, <span class="tt">$If</span> or <span class="tt">$IfOpt</span> was skipped, execute next block, otherwise skip.<br />
* <span class="tt">{$ElseIf ''boolean expression''}</span> : As <span class="tt">$Else</span>, except with an extra expression like <span class="tt">$if</span> to test. There can be multiple <span class="tt">$elseif</span>.<br />
* <span class="tt">{$EndIf}</span> : ends an <span class="tt">$IfDef</span> block<br />
* <span class="tt">{$mode delphi} or {$mode objfpc}</span> : Same as <span class="tt">-Mdelphi</span> or <span class="tt">-Mobjfpc</span>, but only for this unit. You can use units of both modes in a program. If present must be at the top of the unit, or after the module name.<br />
* <span class="tt">{$modeswitch externalclass}</span> : allow declaring external classes<br />
* <span class="tt">{$modeswitch arrayoperators}</span> : allow + operator to concatenate arrays, default in mode delphi<br />
* <span class="tt">{$modeswitch OmitRTTI}</span> : treat published sections as public<br />
* <span class="tt">{$macro on|off}</span> enables macro replacements. Only macros with a value are replaced. Macros are never replaced inside directives.<br />
* <span class="tt">{$I filename} or {$include filename}</span> - insert include file<br />
* <span class="tt">{$I %param%}</span> :<br />
** <span class="tt">%date%</span>: current date as string literal, '[yyyy/mm/dd]'<br />
** <span class="tt">%time%</span>: current time as string literal, 'hh:mm:ss'. Note that the inclusion of <span class="tt">%date%</span> and <span class="tt">%time%</span> will not cause the compiler to recompile the unit every time it is used: the date and time will be the date and time when the unit was last compiled.<br />
** <span class="tt">%file%</span>: current source filename as string literal, e.g. '''unit1.pas'''<br />
** <span class="tt">%line%</span>: current source line number as string literal, e.g. '''123'''<br />
** <span class="tt">%linenum%</span>: current source line number as integer, e.g. ''123''<br />
** <span class="tt">%currentroutine%</span>: name of current routine as string literal<br />
** <span class="tt">%pas2jstarget%, %pas2jstargetos%, %fpctarget%, %fpctargetos%</span>: target os as string literal, e.g. 'Browser'<br />
** <span class="tt">%pas2jstargetcpu%, %fpctargetcpu%</span>: target cpu as string literal, e.g. 'ECMAScript5'<br />
** <span class="tt">%pas2jsversion%, %fpcversion%</span>: compiler version as strnig literal, e.g. '1.0.2'<br />
** If param is none of the above it will use the environment variable. Keep in mind that depending on the platform the name may be case sensitive. If there is no such variable an empty string ''<nowiki>''</nowiki>'' is inserted.<br />
* <span class="tt">{$Warnings on|off}</span><br />
* <span class="tt">{$Notes on|off}</span><br />
* <span class="tt">{$Hints on|off}</span><br />
* <span class="tt">{$Error text}</span> : emit an error<br />
* <span class="tt">{$Warning text}</span> : emit a warning<br />
* <span class="tt">{$Note text}</span> : emit a note<br />
* <span class="tt">{$Hint text}</span> : emit a hint<br />
* <span class="tt">{$Message hint-text}</span> : emit a hint<br />
* <span class="tt">{$Message hint|note|warn|error|fatal text}</span> : emit a message<br />
* <span class="tt">{$Warn identifier on|off|default|error}</span> : enable or disable a specific hint.<br /> Note, that some hints like "Parameter %s not used" are currently using the enable state at the end of the module, not the state at the hint source position.<br /> Identifier can be a message number as written by -vq or one of the following case insensitive:<br /><br />
** <span class="tt">CONSTRUCTING_ABSTRACT</span> : Constructing an instance of a class with abstract methods.<br />
** <span class="tt">IMPLICIT_VARIANTS</span> : Implicit use of the variants unit.<br />
** <span class="tt">NO_RETVAL</span> : Function result is not set<br />
** <span class="tt">SYMBOL_DEPRECATED</span> : Deprecated symbol.<br />
** <span class="tt">SYMBOL_EXPERIMENTAL</span> : Experimental symbol<br />
** <span class="tt">SYMBOL_LIBRARY</span><br />
** <span class="tt">SYMBOL_PLATFORM</span> : Platform-dependent symbol.<br />
** <span class="tt">SYMBOL_UNIMPLEMENTED</span> : Unimplemented symbol.<br />
** <span class="tt">HIDDEN_VIRTUAL</span> : method hides virtual method of ancestor<br />
** <span class="tt">GARBAGE</span> : text after final end.<br />
** <span class="tt">BOUNDS_ERROR</span> : range check errors<br />
** <span class="tt">MESSAGE_DIRECTIVE</span> : user defined $message<br />
* <span class="tt">{$M+}, {$TypeInfo on}</span> : switches default visibility for class members from public to published<br />
* <span class="tt">{$ScopedEnums on|off}</span> disabled(default): propagate enums to global scope, enable: needs fqn e.g. TEnumType.EnumValue.<br />
* <span class="tt">{$C+}</span> generate code for assertions<br />
* <span class="tt">{$H+}</span> , but not <span class="tt">{$H-}</span><br />
* <span class="tt">{$J-}, {$WriteableConst off}</span> : Typed const become readonly. For example ''const i:byte=3; ... i:=4'' creates a compile time error.<br />
* <span class="tt">{$M+}</span> : allow published members<br />
* <span class="tt">{$Q+}</span> : not yet supported, ignored<br />
* <span class="tt">{$R+}, {$RangeChecks on}</span> : compile time range check hints become errors and add runtime range checks for assignments.<br />
* <span class="tt">{$ObjectChecks on|off}</span> :<br />
** Verify method calls, i.e. check at runtime in every method if ''Self'' is a descendant class.<br />
** Check type casts, e.g. ''TBird(AnObject)'' becomes ''AnObject as TBird''<br />
* <span class="tt">{$DispatchField Msg}</span> : enable checking ''message number'' methods for record field name "Msg"<br />
* <span class="tt">{$DispatchStrField MsgStr}</span> : enable checking ''message string'' methods for record field name "Msg"<br />
* <span class="tt">{$Optimization Name}</span> : same as command line option -OoName<br />
* <span class="tt">{$linklib filename [alias]}</span>:<br />
** <span class="tt">filename</span> filename to import.<br />
** <span class="tt">alias</span> alias for this import. Must be a pascal identifier. If no alias is specified, the last part of the filename is used, all non-alphanumeric characters are converted to underscores.<br />
<br />
Defines:<br />
<br />
* PASJS<br />
* PAS2JS_FULLVERSION - major*1000+minor*100+release, e.g. 1.2.3 = 10203<br />
* Target platform: Browser, NodeJS, Pas2JSTargetOS=&lt;value&gt;<br />
* Target processor: ECMAScript5, ECMAScript6, ECMAScript=5, Pas2JSTargetCPU=&lt;value&gt;<br />
* Mode: DELPHI, OBJFPC<br />
<br />
<br />
<br />
==Numbers==<br />
<br />
JavaScript only supports double. All Pascal number types and enum values are mapped to this. A double supports integers from<br /> MinInteger = -$10000000000000;<br /> MaxInteger = $fffffffffffff;<br /> MinDouble = 5.0e-324;<br /> MaxDouble = 1.7e+308;<br /><br /> Intrinsic integer types:<br />
<br />
* Byte - unsigned 8-bit<br />
* ShortInt - signed 8-bit<br />
* Word - unsigned 16-bit<br />
* SmallInt - signed 16-bit<br />
* LongWord - unsigned 32-bit<br />
* LongInt - signed 32-bit<br />
* NativeUInt - unsigned 53-bit<br />
* NativeInt - signed 54-bit<br />
<br />
Notes:<br />
<br />
* Division by zero does not raise an exception. 0/0 results in NaN, positive/0 is Infinity, negative/0 is -Infinity.<br />
* NaN&lt;&gt;NaN<br />
* Overflows work differently. For example in Delphi adding 100 to a byte of 200 gives ''300 and $ff = 44'', while in pas2js it gives 300, which is not a byte anymore.<br />
* Math.isNan(double) tests for NaN. Otherwise false. isNan(Infinity)=false.<br />
* Math.isFinite(double) tests if not NaN, positive or negative infinity.<br />
* Math.isInfinite(double) tests if positive or negative infinity.<br />
* For more functions see unit Math.<br />
* To make porting easier Single is defined in the system unit as alias of double, but gives a warning. Since using higher precision might give unexpected results you should check every place.<br />
<br />
<br />
<br />
==Other supported Pascal elements==<br />
<br />
* '''break''', '''continue''', '''exit''', '''exit()'''<br />
* '''chr''', '''ord'''<br />
* alias type and type alias type<br />
* ''inc()'' and ''dec()'' to += -=<br />
* Converts "a div b" to "Math.floor(a / b)"<br />
* and, or, xor, not: logical and bitwise<br />
* Name conflicts with JS identifiers are automatically fixed by changing case. For example a Pascal function "''apply"'' is renamed to "''Apply''".<br />
* uses unitname in 'filename'. In ''$mode delphi'' the in-filenames are only allowed in the program and the unitname must fit the filename, e.g. ''uses unit1 in 'sub/Unit1.pas''.<br /> In ''$mode objfpc'' units can use in-filenames too and alias are allowed, e.g. ''uses foo in 'bar.pas'''.<br />
* The intrinsic procedure '''str''' works with boolean, integer, float and enumvalue.<br /> Additionally there is '''str''' function, that takes an arbitrary number of arguments and returns a concatenated string. It supports string as parameter too. For example s:=str(i,' ',d:1:5).<br /> Width and precision is supported. str(i:10) will add spaces to the left to fill up to 10 characters. str(aDouble:1:5) returns a string in decimal format with 5 digits for the fraction.<br />
* Intrinsic procedure WriteStr(out s: string; params...)<br />
* ''Debugger;'' converts to ''debugger;''. If a debugger is running it will break on this line just like a break point.<br />
* ''function concat(string1,string2,...): string'' since 1.3<br />
* ''$mode delphi: function lo|hi(integer): byte'' since 1.3<br />
* ''$mode objfpc: function lo|hi(integer): byte|word|longword'' since 1.3<br />
* ''function GetTypeKind(Type or Var): TTypeKind;'' since 1.5<br />
<br />
==Not supported elements==<br />
<br />
* Class destructor<br />
* Enums with custom values<br />
* Global properties<br />
* Futures<br />
* Inline<br />
* Objects<br />
* Operator overloading<br />
* Pointer arithmetic<br />
* Package<br />
* Resources<br />
* RTTI extended, $RTTI<br />
* Variant records<br />
* Variants<br />
<br />
==JavaScript Version==<br />
<br />
Code generation depending on -P option:<br />
<br />
* ECMAScript5<br />
* ECMAScript6: using 0b for binary literals, and 0o for octal literals<br />
<br />
<br />
<br />
==Creating source maps==<br />
<br />
Source maps are files telling the browser what JavaScript comes from which original source (e.g. Pascal file), similar to debug information in FPC/Delphi.<br /> In 2017 FireFox and Chrome supports source maps.<br /> You can enable generating source map files by using the ''-Jm'' option.<br /> The compiler generates one module.js.map file for every generated module.js file. The last line of the .js file contains the line<br />''//# sourceMappingURL=module.js.map''<br /> telling the browser where to find the source map.<br /> The source map contains references to the Pascal files and included .js files (e.g. -Jirtl.js) relative to the location of the source map. Note that if the Pascal file lies in a parent directory, the relativ path contains '../'. You can change the base directory of the relative paths by using the option ''-Jmbasedir=&lt;x&gt;''. For example ''-JmC:\www\pas'' creates paths relative to C:\www\pas.<br /> You can set the base URL, where the browser finds the Pascal sources, by passing the ''-Jmsourceroot=&lt;x&gt;'' option. For example ''-Jmsourceroot=http://www.yoursite.com/pas/''. The browser prepends this to the source map filenames when downloading the original source files (e.g. the .pas files).<br /> You can include the whole Pascal sources in the source map using the option ''-Jminclude''.<br /><br /> To show the generated mapping for each line you can use the tool fpc/packages/fcl-js/examples/srcmapdump.<br /><br />
<br />
* Option -JmXSSIHeader: According to the specifications sourcemap should start with the XSSI (cross site script inclusion) protection header '')]}'''. If your browser does not support that, disable it with ''-JmXSSIHeader-''. See here the specs: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.h7yy76c5il9v<br />
==See Also==<br />
* [[pas2js]]<br />
[[Category:Pas2js]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Pas2js_Transpiler&diff=153499Pas2js Transpiler2022-08-16T15:54:58Z<p>Skalogryz: </p>
<hr />
<div>==About pas2js==<br />
<br />
pas2js is a compiler/transpiler to translate programs written in Pascal (subset of Delphi/ObjFPC syntax) to JavaScript.<br /> The goal is to use strong typing, while still be able to use low level whenever you choose.<br /> The compiled Pascal functions can be used in DOM events or called by JavaScript.<br /> pas2js is written completely in FPC, runs on many platforms like Windows, Mac and Linux and more. It is built modular consisting of the following parts:<br />
<br />
* file cache - loading, caching files, converting to UTF-8<br />
* file resolver - handling search paths, finding used units and include files<br />
* scanner - reading tokens, handling compiler directives like $IfDef and $Include<br />
* parser - reading the tokens, checking syntax, creating Pascal nodes<br />
* resolver - resolving references, type checking and checking duplicate identifiers<br />
* use analyzer - finding unused identifiers, emit hints and warning<br />
* converter - translating Pascal nodes into JavaScript nodes<br />
* compiler - handling config files, parameters, compiling recursively all used units, writes js<br />
* command line interface - a small wrapper to embed the compiler into a console program<br />
* library and interface - a small wrapper to embed the compiler into a library<br />
<br />
Each part is tested separately and is used by other FPC tools as well. For example the scanner and parser are used by fpdoc too. Thus they are tested and extended by other programmers, reducing greatly the work for developing pas2js. Consistency is kept by several test suites, containing thousands of tests. </div><div class="section"> Note: The modular structure allows to compile any parts or the whole compiler into an IDE addon (not yet started). </div><div class="section"><br />
<br />
==Command line parameters==<br />
<br />
Most parameters work the same as their FPC equivalent. pas2js has some options of its own (see -J options).<br />
<br />
<pre> <br />
Options:<br />
Put + after a boolean switch option to enable it, - to disable it<br />
@<x> : Read compiler options from file <x> in addition to the default<br />
pas2js.cfg<br />
-B : Rebuild all<br />
-d<x> : Defines the symbol <x>. Optional: -d<x>:=<value><br />
-i<x> : Write information and halt. <x> is a combination of the following:<br />
-iD : Write compiler date<br />
-iSO : Write compiler OS<br />
-iSP : Write compiler host processor<br />
-iTO : Write target platform<br />
-iTP : Write target processor<br />
-iV : Write short compiler version<br />
-iW : Write full compiler version<br />
-ic : Write list of supported JS processors usable by -P<x><br />
-im : Write list of supported modeswitches usable by -M<x><br />
-io : Write list of supported optimizations usable by -Oo<x><br />
-it : Write list of supported targets usable by -T<x><br />
-iJ : Write list of supported JavaScript identifiers -JoRTL-<x><br />
-C<x> : Code generation options. <x> is a combination of the following<br />
letters:<br />
o : Overflow checking of integer operations<br />
r : Range checking<br />
R : Object checks. Verify method calls and object type casts.<br />
-F... Set file names and paths:<br />
-Fe<x>: Redirect output to file <x>. UTF-8 encoded.<br />
-FE<x>: Set main output path to <x><br />
-Fi<x>: Add <x> to include paths<br />
-FN<x>: add <x> to namespaces. Namespaces with trailing - are removed.<br />
Delphi calls this flag "unit scope names".<br />
-Fu<x>: Add <x> to unit paths<br />
-FU<x>: Set unit output path to <x><br />
-I<x> : Add <x> to include paths, same as -Fi<br />
-J... Extra options of pas2js<br />
-Ja<x>: Append JS file <x> to main JS file. E.g. -Jamap.js. Can be given<br />
multiple times. To remove a file name append a minus, e.g.<br />
-Jamap.js-.<br />
-Jc : Write all JavaScript concatenated into the output file<br />
-Je<x>: Encode messages as <x>.<br />
-Jeconsole: Console codepage. This is the default.<br />
-Jesystem : System codepage. On non Windows console and system are the<br />
same.<br />
-Jeutf-8 : Unicode UTF-8. Default when using -Fe.<br />
-JeJSON : Output compiler messages as JSON. Logo etc are outputted<br />
as-is.<br />
-Ji<x>: Insert JS file <x> into main JS file. E.g. -Jirtl.js. Can be given<br />
multiple times. To remove a file name append a minus, e.g.<br />
-Jirtl.js-.<br />
-Jl : lower case identifiers<br />
-Jm : generate source maps<br />
-Jmsourceroot=<x>: use x as "sourceRoot", prefix URL for source file<br />
names.<br />
-Jmbasedir=<x>: write source file names relative to directory x, default<br />
is map file folder.<br />
-Jminclude: include Pascal sources in source map.<br />
-Jmabsolute: store absolute filenames, not relative.<br />
-Jmxssiheader: start source map with XSSI protection )]}', default.<br />
-Jm-: disable generating source maps<br />
-Jo<x>: Enable or disable extra option. The x is case insensitive:<br />
-JoSearchLikeFPC: search source files like FPC, default: search case<br />
insensitive.<br />
-JoUseStrict: add "use strict" to modules, default.<br />
-JoCheckVersion-: do not add rtl version check, default.<br />
-JoCheckVersion=main: insert rtl version check into main.<br />
-JoCheckVersion=system: insert rtl version check into system unit init.<br />
-JoCheckVersion=unit: insert rtl version check into every unit init.<br />
-JoRTL-<y>=<z>: set RTL identifier y to value z. See -iJ.<br />
-Jr<x> Control writing of resource string file<br />
-Jrnone: Do not write resource string file<br />
-Jrunit: Write resource string file per unit with all resource strings<br />
-Jrprogram: Write resource string file per program with all used<br />
resource strings in program<br />
-JR<x> Control writing of linked resources<br />
-JRnone: Skip resource directives<br />
-JRjs: Write resources in Javascript structure<br />
-JRhtml[=filename] : Write resources as preload links in HTML file <br />
(default is projectfile-res.html)<br />
-Jpcmd<command>: Run postprocessor. For each generated js execute command<br />
passing the js as stdin and read the new js from stdout. This<br />
option can be added multiple times to call several postprocessors<br />
in succession.<br />
-Ju<x>: Add <x> to foreign unit paths. Foreign units are not compiled.<br />
-JU: This pas2js does not support PCU files<br />
-l : Write logo<br />
-M<x> : Set language mode or enable/disable a modeswitch<br />
-MDelphi: Delphi 7 compatibility mode<br />
-MObjFPC: FPC's Object Pascal compatibility mode (default)<br />
-M<x> : enable or disable modeswitch, see option -im<br />
Each mode (as listed above) enables its default set of modeswitches.<br />
Other modeswitches are disabled and need to be enabled one by another.<br />
-NS<x> : obsolete: add <x> to namespaces. Same as -FN<x><br />
-n : Do not read the default config files<br />
-o<x> : Change main JavaScript file to <x>, "." means stdout<br />
-O<x> : Optimizations:<br />
-O- : Disable optimizations<br />
-O1 : Level 1 optimizations (quick and debugger friendly)<br />
-O2 : Level 2 optimizations (Level 1 + not debugger friendly)<br />
-Oo<x>: Enable or disable optimization. The x is case insensitive:<br />
-OoEnumNumbers[-]: write enum value as number instead of name. Default<br />
in -O1.<br />
-OoRemoveNotUsedPrivates[-]: Default is enabled<br />
-OoRemoveNotUsedDeclarations[-]: Default enabled for programs with -Jc<br />
-OoRemoveNotUsedPublished[-] : Default is disabled<br />
-OoShortRefGlobals[-]: Insert JS local var for types, modules and<br />
static functions. Default enabled in -O2<br />
-P<x> : Set target processor. Case insensitive:<br />
-Pecmascript5: default<br />
-Pecmascript6<br />
-S<x> : Syntax options. <x> is a combination of the following letters:<br />
2 : Same as -Mobjfpc (default)<br />
a : Turn on assertions<br />
c : Support operators like C (*=,+=,/= and -=)<br />
d : Same as -Mdelphi<br />
j : Allows typed constants to be writeable (default)<br />
m : Enables macro replacements<br />
-SI<x> : Set interface style to <x><br />
-SIcom : COM, reference counted interface (default)<br />
-SIcorba: CORBA interface<br />
-T<x> : Set target platform<br />
-Tbrowser: default<br />
-Tnodejs : add pas.run(), includes -Jc<br />
-Telectron: experimental<br />
-Tmodule : add pas.run(), includes -Jc<br />
-u<x> : Undefines the symbol <x><br />
-v<x> : Be verbose. <x> is a combination of the following letters:<br />
e : Show errors (default)<br />
w : Show warnings<br />
n : Show notes<br />
h : Show hints<br />
i : Show info<br />
l : Show line numbers, needs -vi<br />
a : Show everything<br />
0 : Show nothing (except errors)<br />
b : Show file names with full path<br />
c : Show conditionals<br />
t : Show tried/used files<br />
d : Show debug notes and info, enables -vni<br />
q : Show message numbers<br />
x : Show used tools<br />
v : Write pas2jsdebug.log with lots of debugging info<br />
z : Write messages to stderr, -o. still uses stdout.<br />
-vm<x>,<y>: Do not show messages numbered <x> and <y>.<br />
-? : Show this help<br />
-h : Show this help<br />
<br />
Environment variable PAS2JS_OPTS is parsed after default config and before<br />
command line parameters.<br />
</pre><br />
<br />
==Delphi and ObjFPC mode==<br />
<br />
===Delphi mode===<br />
<br />
* Defines macro ''DELPHI''<br />
* Assigning a function to a function type variable does not require the @ operator. For example, you can write either ''OnGetThing:=GetValue;'' or ''OnGetThing:=@GetValue;''.<br />
* A function type variable reference without brackets is treated as a call. For example: If ''OnGetThing'' is a variable of type ''function: integer'' you can write: ''If OnGetThing=3 then ;''.<br />
* You must use the @@ operator to get the procedure address (i.e. JS reference) of a procedure type variable. For example instead of ''If OnClick=nil then ;'' you must use ''if @@OnClick=nil then ;''.<br />
* Every procedure/method overload needs the 'overload' modifier.<br />
<br />
===ObjFPC mode===<br />
<br />
This the default mode of pas2js and is generally more strict than the Delphi mode, and allows some more operations.<br />
<br />
* Defines macro ''OBJFPC''<br />
* Assigning a function to a function type variable requires the @ operator. For example: ''OnGetThing:=@GetValue;''.<br />
* A function type variable always needs brackets to be called. For example: If ''OnGetThing'' is a variable of type ''function: integer'' then this is allowed: ''If OnGetThing()=3 then ;''. While this gives an error: ''If OnGetThing=3 then ;''.<br />
* You can compare a procedure type with ''nil''. For example ''If OnClick=nil then ;''.<br />
* You can compare a procedure type with a procedure address (i.e. JS reference). For example ''If OnClick=@OnFormClick then ;''.<br />
* The procedure modifier 'overload' can be omitted when all overloads are in one scope, e.g. a unit or a class. And if one procedure has such modifier all procedures with same name and in same scope are overloads as well.<br />
<br />
</div><div class="section"><br />
<br />
==Translating modules==<br />
<br />
A Pascal Program is translated into the following JavaScript structure:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript Structure, not code!<br />
|-<br />
|<br />
<pre><br />
Program &lt;unitname&gt;;<br />
Implementation<br />
[implementation section]<br />
Begin<br />
[main code]<br />
End.<br />
</pre><br />
|<br />
<pre><br />
pas.&lt;program&gt;={<br />
[implementation section],<br />
$main: function() {<br />
[main code]<br />
}<br />
};<br />
</pre><br />
|}<br />
<br />
A Pascal Unit is translated into the following JavaScript structure:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript Structure, not code!<br />
|-<br />
|<br />
Unit &lt;unitname&gt;;<br />
Interface<br />
[interface section]<br />
Implementation<br />
[implementation section]<br />
Initialization<br />
[initialization section]<br />
End.<br />
|<br />
pas.&lt;unitname&gt;={<br />
[interface section],<br />
$impl: {<br />
[implementation section],<br />
},<br />
$init: function() {<br />
[initialization section]<br />
}<br />
};<br />
|}<br />
<br />
Note: The '''finalization''' section is not supported by pas2js.<br /></div><div class="section"> To create and initialize the units in topological order the compiler translates an Unit to the following JavaScript code:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit &lt;unitname&gt;;<br />
Interface<br />
[interface section]<br />
Implementation<br />
[implementation section]<br />
Initialization<br />
[initialization section]<br />
End.<br />
|<br />
rtl.module('&lt;unitname&gt;',<br />
['system',...other used units of the interface section...],<br />
function(){<br />
var $mod = this;<br />
var $impl = $mod.$impl;<br />
[interface section]<br />
$mod.$implcode = function(){<br />
[implementation section]<br />
}<br />
$mod.$init = function(){<br />
[initialization section]<br />
};<br />
},<br />
[...used units of the implementation section]<br />
};<br />
|}<br />
<br />
Here is a more detailed example to make it more clear:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses Sysutils;<br />
var<br />
dIntf: double;<br />
sIntf: string = 'abc';<br />
procedure MyIntfProc;<br />
Implementation<br />
Uses Classes;<br />
Var dImpl:double;<br />
Procedure MyIntfProc;<br />
Begin<br />
dImpl:=dIntf;<br />
End;<br />
Procedure MyImplProc;<br />
Begin<br />
dImpl:=dIntf;<br />
End;<br />
Initialization<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","SysUtils"],<br />
function(){<br />
var $mod = this;<br />
var $impl = $mod.$impl;<br />
this.dIntf = 0.0;<br />
this.sIntf = "abc";<br />
this.MyIntfProc = function(){<br />
$impl.dImpl = $mod.dIntf;<br />
};<br />
$mod.$implcode = function(){<br />
$impl.dImpl = 0.0;<br />
$impl.MyImplProc = function() {<br />
$impl.dImpl = $mod.dIntf;<br />
};<br />
}<br />
$mod.$init = function() {<br />
};<br />
},<br />
["Classes"]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Unit ''System'' is always loaded implicitely.<br />
* References to other units are translated to full path. For example ''TObject'' is translated to ''pas.system.TObject''<br />
* References to dotted unitnames, aka units with namespaces are translated to ''pas["namespace.unitname"]''.<br />
<br />
<br />
<br />
==Translating variables==<br />
<br />
Variables are converted without type, because JavaScript lacks a clear type. They are however always initialized, which helps JavaScript engines to optimize.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses Classes,Forms;<br />
const<br />
c1:integer=3;<br />
c2 = 'abc';<br />
c3 = 234;<br />
c4 = 12.45;<br />
c5 = nil;<br />
var<br />
v1:string;<br />
v2,v3:double;<br />
v4:byte=0;<br />
v5:TForm;<br />
v6:TIdentMapEntry;<br />
v7:string='abc';<br />
v8:char='c';<br />
v9:array of byte;<br />
Implementation<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","Classes","Forms"],<br />
function(){<br />
this.c1 = 3;<br />
this.c2 = "abc";<br />
this.c3 = 234;<br />
this.c4 = 12.45;<br />
this.c5 = null;<br />
this.v1 = "";<br />
this.v2 = 0.0;<br />
this.v3 = 0.0;<br />
this.v4 = 0;<br />
this.v5 = null;<br />
this.v6 = new pas.Classes.TIdentMapEntry();<br />
this.v7 = "abc";<br />
this.v8 = "c";<br />
this.v9 = [];<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Type casting a ''boolean'' to ''integer'', gives ''0'' for ''false'' and ''1'' for ''true''.<br />
* Type casting an ''integer'' to ''boolean'', gives ''false'' for ''0'' and ''true'' otherwise.<br />
* A '''char''' is translated to a JS string, because JS lacks a native char type.<br />
* A '''char''' is a single JS char code. An UTF-16 codepoint can contain one or two '''char'''.<br />
* '''Integers overflows''' at runtime differ from Delphi/FPC, due to the double format. For example adding ''var i: byte = 200; ... i:=i+100;'' will result in ''i=300'' instead of ''i=44'' as in Delphi/FPC. When range checking ''{$R+}'' is enabled ''i:=300'' will raise an ERangeError.<br />
* '''type cast integer to integer''', e.g. ''byte(aLongInt)''<br />
** with range checking enabled: error if outside range<br />
** without range checking: emulates the FPC/Delphi behaviour: e.g. ''byte(value)'' translates to ''value &amp; 0xff'', ''shortint(value)'' translates to ''value &amp; 0xff &lt;&lt;24 &gt;&gt; 24.''<br />
* The '''mod-operator''' works 32-bit signed in JS.<br />
<br />
==Translating string==<br />
<br />
Strings are translated to JavaScript strings. They are initialized with "" and are never '''null'''.<br /> There are no ''ShortString, AnsiString or RawByteString''. ''Unicodestring'' and ''Widestring'' are alias of ''String''.<br /> JavaScript strings are immutable, which means that changing a single character in a string, creates a new string. So a ''s[2]:='c';'' is a slow operation in pas2js compared to Delphi/FPC.<br /> Although pas2js creates .js files encoded as UTF-8 with BOM, JavaScript strings are UTF-16 at runtime. Keep in mind that one UTF-16 codepoint can need two ''char'', and a visible glyph can need several codepoints. Same as in Delphi.<br />
<br />
==Translating resourcestrings==<br />
<br />
Resourcestrings are translated to JS objects with original (org) and current value.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
resourcestring<br />
rsCompiler = 'pas2js';<br />
var<br />
s:string;<br />
Implementation<br />
initialization<br />
s:=rsCompiler;<br />
End.<br />
|<br />
rtl.module("test1",["System"],function () {<br />
var $mod = this;<br />
this.s = "";<br />
$mod.$resourcestrings = {rsCompiler: {org: "pas2js"}};<br />
$mod.$init = function () {<br />
$mod.s = rtl.getResStr(pas.test1,"rsCompiler");<br />
};<br />
});<br />
|}<br />
<br />
<br />
<br />
==Translating currency==<br />
<br />
''Currency'' in Delphi/FPC is an int64 with a factor of 10000. This is translated to a double with factor 10000 and truncated.<br />
<br />
* ''CurA := 1.12345'' -&gt; ''CurA = 11234''<br />
* ''CurA + CurB'' -&gt; ''CurA + CurB''<br />
* ''CurA * CurB'' -&gt; ''CurA * CurB/10000''<br />
* ''CurA / CurB'' -&gt; ''Math.floor(CurA/CurB * 10000)''<br />
* ''CurA ^^ CurB'' -&gt; ''Math.floor(Math.pow(CurA/10000,CurB/10000) * 10000)''<br />
* ''Currency + Double'' -&gt; ''Currency + (Double*10000)''<br />
* ''Double := Currency'' -&gt; ''Double = Currency/10000''<br />
* ''Currency := Double'' -&gt; ''Currency = Math.floor(Double*10000)''<br />
* ''JSValue := Currency'' -&gt; ''JSValue = Currency/10000''<br />
* Keep in mind that a double has only 54 bits for the number, so calculating values greater than 900,719,925,474 might give a different result than in Delphi/FPC. See SysUtils.MinCurrency/MaxCurrency<br />
<br />
<br />
<br />
==Translating Types==<br />
<br />
JavaScript type design has no declarative form, except for object types (so-called prototypes). That's why all the derivatives from simple Pascal types can not be translated. The compiler ensures type safety at compile time though, which is a big plus for using Pascal.<br /> Complex Pascal types (classes, records, or arrays) are translated into JavaScript objects or arrays respectively.<br /><br />
<br />
==Translating pointer==<br />
<br />
A ''pointer'' is translated to a JS reference. It can be assigned a class, a class instance, a class-of, an array, a procedure var, a method var, a @proc address, a @method address, or a pointer of record. There is no pointer arithmetic, i.e. no p+1, and no typed pointers, except for pointer of record. You can find out its type using the functions ''isArray'', ''isClass'', ''isClassRef'', ''isCallback'', etc of unit ''JS''.<br />
<br />
==Translating record type==<br />
<br />
A record is translated to a JavaScript object.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JS Pas2js 1.3<br />
! JS Pas2js 1.2<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TMyRecord = Record<br />
i: integer;<br />
s: string;<br />
d: TDateTime;<br />
End;<br />
Var<br />
r, s: TMyRecord;<br />
Implementation<br />
Initialization<br />
r.i := 123;<br />
r:=s;<br />
if r=s then ;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.recNewT($mod, "TMyRecord", function() {<br />
this.i = 0;<br />
this.s = "";<br />
this.d = 0.0;<br />
this.$eq = function (b) {<br />
return (this.i == b.i) &amp;&amp;<br />
(this.s == b.i) &amp;&amp; (this.d == b.d);<br />
};<br />
this.$assign = function (s) {<br />
this.i = s.i;<br />
this.s = s.s;<br />
this.d = s.d;<br />
return this;<br />
};<br />
};<br />
this.r = this.TMyRecord.$new();<br />
$mod.$init = function() {<br />
$mod.r.i=123;<br />
$mod.r.$assign($mod.s);<br />
if ($mod.r.$eq($mod.s)) ;<br />
},<br />
},<br />
[]);<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TMyRecord = function(s) {<br />
if (s){<br />
this.i = s.i;<br />
this.s = s.s;<br />
this.d = s.d;<br />
} else {<br />
this.i = 0;<br />
this.s = "";<br />
this.d = 0.0;<br />
};<br />
this.$equal = function (b) {<br />
return (this.i == b.i) &amp;&amp;<br />
(this.s == b.i) &amp;&amp; (this.d == b.d);<br />
};<br />
};<br />
this.r = new this.TMyRecord();<br />
$mod.$init = function() {<br />
$mod.r.i=123;<br />
$mod.r = new $mod.TMyRecord($mod.s);<br />
if ($mod.r.$equal($mod.s)) ;<br />
},<br />
},<br />
[]);<br />
|}<br />
<br />
* The record variable creates a JavaScript object.<br />
* Variant records are not supported.<br />
* Supported: Assign, pass as argument, equal, not equal, array of record, pointer of record, const, default(), RTTI.<br />
* Advanced record (since pas2js 1.3):<br />
** visibility private, strict private, public, default is public<br />
** methods, class methods (must be static like in Delphi/FPC)<br />
** class vars<br />
** const fields<br />
** property, class property, array property, default array property<br />
** sub types<br />
** constructor<br />
** class constructor<br />
* Not yet implemented:<br />
** operator overloading<br />
** reference counted interfaces as fields<br />
** Interfaces as nested types<br />
** default non array property<br />
* Until Pas2js 1.2 when assigning a record it is cloned, creating a new JS object. Since Pas2js 1.3 only values are copied, keeping the object, so pointer of record is compatible.<br />
* Since record types are JS objects it is possible to typecast a record type to the JS Object, e.g. ''TJSObject(TPoint)''. Note that you cannot typecast directly to a ''TJSObject'' descendant. You can use ''TJSWindow(TJSObject(aRecord))''.<br />
* A pointer of record is simply a reference.<br />
** ''p:=@r'' translates to ''p=r''<br />
** ''p^.x'' becomes ''p.x''.<br />
** ''New(PointerOfRecord)'' creates a new record<br />
** ''Dispose(PointerOfRecord)'' Sets the variable to null if possible.<br />
* <nowiki>Passing a record to an untyped arguments (e.g. ''TObject.Dispatch(var Msg)'') passes the record JS object directly, not creating a temporary reference object.</nowiki><br />
* Typecasting RecordType(UntypedArgument) returns the argument, i.e. no conversion.<br />
<br />
<br />
<br />
==Translating functions==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function DoubleIt(n: integer): integer;<br />
Implementation<br />
Function DoubleIt(n: integer): integer;<br />
Begin<br />
Result:=2*n;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.DoubleIt = function(n){<br />
Result = 0;<br />
Result = 2*n;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Local variables become local JavaScript variables: ''var l = 0;''.<br />
* Local constants become JavaScript variables in the unit/program implementation section.<br />
* Local types are elevated to module.<br />
* Overloaded functions are given an unique name by appending $1, $2, ...<br />
* Supported: default values, const/var/out/default, FuncName:=<br />
<br />
<br />
<br />
==Translating passing a parameter by reference==<br />
<br />
JavaScript lacks passing by reference. Instead a temporary object is created with a ''get'' and ''set'' function. That means changes within the procedure are immediately visible outside, compatible with Pascal.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure DoubleIt(var n: integer);<br />
Begin<br />
n:=2*n;<br />
End;<br />
Function Doubling(n: integer): integer;<br />
Begin<br />
DoubleIt(n);<br />
Result:=n;<br />
End;<br />
Var<br />
i: integer = 7;<br />
Begin<br />
Doubling(i);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 7;<br />
this.DoubleIt = function(n){<br />
n.set(2*n.get());<br />
};<br />
this.Doubling = function(n){<br />
var Result = 0;<br />
DoubleIt({<br />
get:function(){<br />
return n<br />
},<br />
set:function(v){<br />
n=v;<br />
}<br />
});<br />
Result = n;<br />
return n;<br />
};<br />
$mod.$main = function(){<br />
Doubling($mod.i);<br />
}<br />
},<br />
[]);<br />
|}<br />
<br />
When the passed value is from another context, the context is passed too:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure DoubleIt(var n: integer);<br />
Begin<br />
n:=2*n;<br />
End;<br />
Var<br />
i: integer = 7;<br />
Begin<br />
DoubleIt(i);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 7;<br />
this.DoubleIt = function(n){<br />
n.set(2*n.get());<br />
};<br />
$mod.$main = function(){<br />
DoubleIt({<br />
p:$mod,<br />
get:function(){<br />
return this.p.i<br />
},<br />
set:function(v){<br />
this.p.i=v;<br />
}<br />
});<br />
}<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Contrary to Delphi/FPC it is allowed to pass a property to a var/out parameter.<br />
<br />
<br />
<br />
==Translating nested functions==<br />
<br />
A nested function is translated to a local variable.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n, Adder: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n, Adder: integer): integer;<br />
<br />
Function Add(k: integer): integer;<br />
Begin<br />
if k=1 then<br />
Result:=1<br />
else<br />
Result:=Add(k-1)+Adder;<br />
End;<br />
<br />
Begin<br />
Result:=Add(n);<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.DoubleIt = function(n,Adder){<br />
Result = 0;<br />
var Add = function(k) {<br />
Result = 0;<br />
if (k==1) {<br />
Result = 1;<br />
} else {<br />
Result = Add(k-1)+Adder;<br />
}<br />
return Result;<br />
};<br />
Result = Add(n);<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: You can assign a nested procedure to a procedure variable. A nested procedure of a method can be assigned to a method variable.<br /> JavaScript preserves the current local scope, including references to the local variables of parent functions. Local types and constants belong to the unit scope (singleton).<br /> When a method has nested functions, the compiler adds a local var ''Self''.<br />
<br />
==Translating for-loops==<br />
<br />
The JavaScript for-loop executes the end expression every iteration, while Pascal only executes it once. Therefore a local variable is introduced. If the loop is not entered at all, the variable is not touched. If the loop was entered the variable contanis the last value.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
For i:=1 To n Do<br />
Begin<br />
j:=j+i;<br />
End;<br />
if i&lt;1 then j:=1;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
Result = 0;<br />
j = 0;<br />
for (var $l1 = 1, $le2 = n; $l1 &lt;= $le2; $l1++) {<br />
i = $l1;<br />
j = j + i;<br />
};<br />
if (i&lt;1) j=1;<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: The after-loop decrement is only added if ''i'' is read after the loop.<br /><br />
<br />
==Translating repeat..until==<br />
<br />
The ''repeat..until'' is translated to a ''do{}while()''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
i:=0;<br />
Repeat<br />
i:=i+1;<br />
j:=j+i;<br />
Until i&gt;=n;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
Result = 0;<br />
j = 0;<br />
i = 0;<br />
do{<br />
i = (i + 1);<br />
j = (j + i);<br />
} while (!(i&gt;=n));<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating while..do==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Function SumNNumbers(n: integer): integer;<br />
Implementation<br />
Function SumNNumbers(n: integer): integer;<br />
Var<br />
i, j: integer;<br />
Begin<br />
j:=0;<br />
i:=0;<br />
While i&lt;n Do Begin<br />
i:=i+1;<br />
j:=j+i;<br />
End;<br />
Result:=j;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
this.SumNNumbers=function(n){<br />
var Result = 0;<br />
var j = 0;<br />
var i = 0;<br />
while(i&lt;n){<br />
i = (i + 1);<br />
j = (j + i);<br />
};<br />
Result = j;<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating case..do==<br />
<br />
Although JavaScript has something similar in form of the "switch" statement, it lacks ranges and is on current JS engines often slower than "if-else". Therefore a case..of is translated to if..else.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Var<br />
i: integer;<br />
Begin<br />
case i of<br />
1: ;<br />
2: i:=3;<br />
else<br />
i:=4;<br />
end;<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.i = 0;<br />
$mod.$main=function(n){<br />
$tmp1 = $mod.i;<br />
if ($tmp1 == 1){<br />
} else if ($tmp1 == 2) {<br />
i=3;<br />
} else {<br />
i=4;<br />
}<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==Translating with..do==<br />
<br />
JavaScript has a '''with''', but it is slow and deprecated. Instead a temporary variable is used:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TClassA = class<br />
i: integer;<br />
end;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
begin<br />
with TClassA.Create do<br />
i:=3;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod, "TClassA", pas.System.TObject, function () {<br />
this.$init = function () {<br />
this.i = 0;<br />
};<br />
});<br />
this.DoIt = function(){<br />
var $with1 = $mod.TClassA.$create("Create");<br />
$with1.i = 3;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note: If the with-expression is already a local variable no new variable is created. This is Delphi/FPC compatible.<br />
<br />
==Translating enums==<br />
<br />
Enum values are translated to numbers. The enum type is translated to an object containing a mapping from name to number and number to name.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TMyEnum = (<br />
Red,<br />
Green,<br />
Blue);<br />
var<br />
e: TMyEnum = Blue;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
begin<br />
e := Green;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TMyEnum = {<br />
"0":"Red",<br />
Red:0,<br />
"1":"Green",<br />
Green:1,<br />
"2":"Blue",<br />
Blue:2<br />
};<br />
this.e = $mod.TMyEnum.Blue;<br />
this.DoIt = function(){<br />
$mod.e = $mod.TMyEnum.Green;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Supported: ord(), low(), high(), pred(), succ(), type cast number to enum.<br />
* With optimization level -O1 the compiler uses numbers instead of names.<br />
* Not yet implemented: custom values for enum values.<br />
<br />
<br />
<br />
==Translating sets==<br />
<br />
A set s is translated to a JavaScript object, where for each included enum holds ''s.enumvalue==true''. This allows arbitrary large sets and the ''in'' operator is fast.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
type<br />
TColor = (Red, Green, Blue);<br />
TColors = set of TColor;<br />
<br />
procedure DoIt;<br />
<br />
Implementation<br />
<br />
procedure DoIt;<br />
var<br />
c: TColor;<br />
S, T: TColors;<br />
b: boolean;<br />
begin<br />
S:=T;<br />
b:=Red in S;<br />
Include(S,Blue);<br />
Exclude(S,Blue);<br />
S:=S+T;<br />
S:=S-[Red,c];<br />
b:=c in [Red..Blue];<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.TColor = {<br />
"0":"Red",<br />
Red:0,<br />
"1":"Green",<br />
Green:1,<br />
"2":"Blue",<br />
Blue:2<br />
};<br />
$mod.DoIt = function(){<br />
var c = 0;<br />
var S = {};<br />
var T = {};<br />
var b = false;<br />
S = rtl.refSet(T);<br />
b = $mod.TColor.Red in S;<br />
S = rtl.includeSet(S,$mod.TColor.Blue);<br />
S = rtl.excludeSet(S,$mod.TColor.Blue);<br />
S = rtl.unionSet(S,T);<br />
S = rtl.diffSet(S,rtl.createSet($mod.TColor.Red,c));<br />
b = c in rtl.createSet(null,$mod.TColor.Red,$mod.TColor.Blue);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Supported:<br />
** Include<br />
** Exclude<br />
** literal<br />
** literal range, e.g. ''[EnumA..EnumB], ['a'..'z']''<br />
** union +<br />
** difference -<br />
** intersect *<br />
** symmetrical difference &gt;&lt;<br />
** equal =<br />
** unequal &lt;&gt;<br />
** subset &lt;=<br />
** superset &gt;=<br />
** set of anonymous enum type: ''set of (enum1,enum2,...)''<br />
* Not supported: set of char, set of boolean<br />
* There is no optimization yet for small sets like in Delphi/FPC.<br />
* Assigning a set or passing the set as an argument only creates a reference and marks the set as ''shared''. When a ''shared'' set is altered with Include/Exclude a new set is created (copy on write).<br />
* Passing a set as an argument might clone the set. Use the ''const'' modifier for parameters whenever possible.<br />
* Constant sets in expressions (e.g. ''if c in ['a'..'z'] then'') are not yet optimized and created every time. Create a ''const'' to avoid this.<br />
<br />
<br />
<br />
==Translating array type==<br />
<br />
All arrays are translated into JavaScript arrays.<br /> Contrary to Delphi/FPC dynamic arrays are not reference counted and do not copy on write. That means if you pass an array to a procedure and change an element, the original array is changed.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TIntArr = Array of integer;<br />
TObjArr = Array of TObject;<br />
TRec = record c: char; end;<br />
TRecArr = Array of TRec;<br />
Procedure Test;<br />
Implementation<br />
Procedure Test;<br />
Var<br />
IntArr: TIntArr = (1,2,3);<br />
ObjArr: TObjArr;<br />
RecArr: TRecArr;<br />
Begin<br />
IntArr:=nil;<br />
SetLength(IntArr,4);<br />
IntArr[2]:=2;<br />
IntArr[1]:=length(IntArr);<br />
SetLength(ObjArr,5);<br />
SetLength(RecArr,2,TRec);<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.Test = function(){<br />
this.TRec = function(s){<br />
if (s){<br />
this.c = s.c;<br />
} else {<br />
this.c = "";<br />
};<br />
this.$equal = function(b){<br />
return (this.c == b.c);<br />
};<br />
};<br />
this.IntArr = [1,2,3];<br />
this.ObjArr = [];<br />
this.RecArr = [];<br />
this.Test = function(){<br />
$mod.IntArr = [];<br />
rtl.arraySetLength($mod.IntArr,4,0);<br />
$mod.IntArr[2] = 2;<br />
$mod.IntArr[1] = $mod.IntArr.length;<br />
rtl.setArrayLength($mod.ObjArr,5,null);<br />
rtl.setArrayLength($mod.RecArr,2,$mod.TRec);<br />
}<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Supported features of dynamic arrays: SetLength(), Length(), equal/notequal nil, low(), high(), assigned(), concat(), copy(), insert(), delete(), multi dimensional, array of record<br />
* Dynamic array constants. E.g. in mode ObjFPC ''const a: array of byte = (1,2)''. In mode Delphi you must use square brackets, ''... = [1,2]''<br />
* Supported features of static arrays: length(), low(), high(), assigned(), concat(), copy(), const, const records<br />
* Open arrays are implemented as dynamic arrays.<br />
* Calling ''Concat()'' with only one array simply returns the array (no cloning). Calling it with multiple arrays creates a clone. This is Delphi 10.1 compatible.<br />
* In Delphi/FPC an empty array is ''nil''. In JS it can be ''null'' or ''[]''. For compatibility comparing an array with ''nil'' checks for ''length(a)&gt;0''.<br />
* ''function Assigned(array): boolean'' results true iff ''length(array)&gt;0''.<br />
* array of const:<br />
** Works the same: vtInteger, vtBoolean, vtPointer, vtObject, vtClass, vtWideChar, vtInterface, vtUnicodeString<br />
** <nowiki>''longword'' is converted to ''vtNativeInt''. Delphi/FPC converts to ''vtInteger'', changing big numbers to negative numbers.</nowiki><br />
** vtExtended is double, Delphi/FPC: PExtended<br />
** vtCurrency is currency, Delphi/FPC: PCurrency<br />
** Not supported: vtChar, vtString, vtPChar, vtPWideChar, vtAnsiString, vtVariant, vtWideString, vtInt64, vtQWord<br />
** only in pas2js: vtNativeInt, vtJSValue<br />
* Assignation using constant array, e.g. ''a:=[1,1,2];''<br />
* String like operation: + operator concatenates arrays. e.g. ''a:=[1]+[2];''. This is controlled by modeswitch arrayoperators, which is enabled in mode delphi.<br />
* ''function copy(array,start=0,count=max): array''<br />
* ''procedure insert(item,var array,const position)''<br />
* ''procedure delete(var array,const start,count)''<br />
<br />
<br />
<br />
==Translating class type==<br />
<br />
Classes are implemented using ''Object.create'' and some rtl magic.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TClassA = Class(TObject)<br />
Public<br />
i: integer;<br />
Procedure Add(a: integer);<br />
End;<br />
var<br />
ObjA: TClassA;<br />
Implementation<br />
Procedure TClassA.Add(a: integer);<br />
Begin<br />
i:=i+a;<br />
End;<br />
Initialization<br />
ObjA:=TClassA.Create;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TClassA",pas.System.TObject,function(){<br />
this.$init = function () {<br />
this.i = 0;<br />
};<br />
this.Add = function(a){<br />
this.i = this.i + a;<br />
};<br />
});<br />
this.ObjA = null;<br />
$mod.$init = function(){<br />
$mod.ObjA = $mod.TClassA.$create("Create");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Each class and each instance is an JS object.<br />
* Each class has a globally unique JS object, created by rtl.createClass.<br />
* ''Self'' is never ''nil''.<br />
* The method ''TObject.Free'' is using compiler magic. See [#tobjectfree Translating TObject.Free].<br />
* ''Class.$class'' is a reference to the class itself.<br />
* ''Class.$ancestor'' is a reference to the ancestor class.<br />
* A class has ''c.$ancestor == Object.getPrototypeOf(c)''.<br />
* A class instance has ''o.$class == Object.getPrototypeOf(o)''.<br />
* ''Class.$classname'' is the short name. E.g. ''TClassA.$classname == 'TClassA'''.<br />
* ''Class.$name'' is the long name. E.g. ''TClassA.$name == 'MyModule.TClassA'''.<br />
* ''Class.$unitname'' is the unit name. E.g. ''TClassA.$unitname == 'MyModule'''.<br />
* The "''is''"-operator is implemented using "''isPrototypeOf''". Note that "''instanceof''" cannot be used, because classes are JS objects.<br />
* The "''as''" operator is implemented as ''rtl.as(Object,Class)''.<br />
* Supported:<br />
** constructor, destructor<br />
** private, protected, public, strict private, strict protected<br />
** class vars, const, nested types<br />
** methods, class methods, class constructor, external methods<br />
** method modifiers overload, reintroduce, virtual, override, abstract, static, external name, message integer, message string<br />
** call inherited<br />
** assigned()<br />
** type cast<br />
** class sealed, class abstract<br />
* Not supported: class destructor<br />
* Property:<br />
** References are replaced by getter/setter.<br />
** Supported: argument lists, default property, class property, stored modifier, index modifier.<br />
** Not supported: getter/setter to an array element, e.g. ''property A: char read FArray[0];''<br />
** Class property getter/setter can be static or non static. Delphi: must be static.<br />
** The ''Index'' modifier supports any constant, e.g. a string, while Delphi only allows an ordinal (longint). -2147483648 is not a special number in pas2js. Overriding a property with an index property is allowed in Delphi and pas2js.<br />
<br />
<br />
<br />
==Translating class-of type==<br />
<br />
A class-of is a reference to a class. See above about translating class.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Type<br />
TBird = Class(TObject)<br />
Public<br />
Class var Count: integer;<br />
Class Procedure Add(a: integer); virtual;<br />
End;<br />
TBirds = class of TBird;<br />
<br />
TPigeon = Class(TBird)<br />
Public<br />
Class Procedure Add(a: integer); override;<br />
End;<br />
<br />
var<br />
BirdType: TBirds;<br />
Implementation<br />
Class Procedure TBird.Add(a: integer);<br />
Begin<br />
Count:=Count+a;<br />
End;<br />
Class Procedure TPigeon.Add(a: integer);<br />
Begin<br />
inherited Add(a+1);<br />
End;<br />
Initialization<br />
BirdType:=TPigeon;<br />
BirdType.Add(1);<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TBird",pas.System.TObject,function () {<br />
this.Count = 0;<br />
this.Add = function (a) {<br />
this.Count = this.Count + a;<br />
};<br />
});<br />
rtl.createClass($mod,"TPigeon",$mod.TBird,function () {<br />
this.Add = function (a) {<br />
$mod.TBird.Add.call(this,a + 1);<br />
};<br />
});<br />
$mod.$init = function(){<br />
$mod.BirdType = $mod.TPigeon;<br />
$mod.BirdType.Add(1);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Note that ''this'' in a class method is the class itself.<br /><br /> Notes:<br /><br />
<br />
* Contrary to Delphi/FPC the "is" operator works with class-of.<br />
<br />
<br />
<br />
==Translating TObject.Free==<br />
<br />
In Delphi/FPC AnObject.Free checks if Self is nil, then calls the destructor and frees the memory, without changing the reference. In JavaScript however calling a method with AnObject=nil causes a crash. And memory cannot be freed explicitely. Memory is only freed if all references are gone (e.g. set to ''null'').<br /> Therefore pas2js adds code to call the destructor and sets the variable to ''nil'':<br /><br />
<br />
* ''Obj.Free'' on a local variable or argument is translated to ''Obj = rtl.freeLoc(Obj);''.<br />
* ''Obj.Free'' on a non local variable is translated to ''rtl.free(this,"Obj");''.<br />
* Not supported: Freeing a property or function result.<br /> For example ''List[i].Free'' gives a compiler error. The property setter might create side effects, which would be incompatible to Delphi/FPC.<br />
<br />
Notes:<br />
<br />
* If the destructor raises an exception, the variable is not set to ''nil''. This is compatible to Delphi/FPC, where the memory is not freed in this case.<br />
* Alternatively you can use ''FreeAndNil'', which first changes the variable to ''nil'' and then calls the destructor.<br />
<br />
<br />
<br />
==Translating class interfaces==<br />
<br />
JavaScript has nothing like it, so they are emulated.<br /> An interfacetype is a JS-object with some hidden properties, containing the GUID ($guid) and an array with the method names ($names). Here is how IUnknown looks like in JS:<br /><br />
<br />
<br />
{<br />
$module: [object Object],<br />
$name: "IUnknown",<br />
$fullname: "System.IUnknown",<br />
$guid: "{00000000-0000-0000-C000-000000000046}",<br />
$names: ["QueryInterface","_AddRef","_Release"],<br />
$rtti: [object Object],<br />
$kind: "com",<br />
}<br />
<br />
A class implementing interfaces has a variable ''$intfmaps'', which has for each implemented GUID a map or delegator function. A map is a JS instance of the interfacetype plus a for each method name a function to call the class method. Here is an example map of ''IUnknown'' of ''TInterfacedObject'':<br /><br />
<br />
<br />
{<br />
QueryInterface: function (){ return fn.apply(this.$o,arguments); },<br />
_AddRef: function (){ return fn.apply(this.$o,arguments); },<br />
_Release: function (){ return fn.apply(this.$o,arguments); },<br />
...<br />
}<br />
<br />
When an interface is created for an object (here: a Pascal class instance), for example by using the ''as''-operator "''ObjVar as IUnknown''", a JS object is created, which is an instance of the map object with its ''$o'' set to the ''ObjVar''.<br /><br /> Supported:<br />
<br />
* methods, properties, default property<br />
* ''{$interfaces com|corba|default}''<br /><br />
** COM is default, default ancestor is IUnknown (mode delphi: IInterface), managed type, i.e. automatically reference counted via _AddRef, _Release, the checks for support call QueryInterface<br />
** CORBA: lightweight, no automatic reference counting, no default ancestor, fast support checks.<br />
* inheriting<br />
* An interface without a GUID gets one autogenerated from its name and method names.<br />
* Contrary to Delphi/FPC you can assign an interface type or var to the type TGuidString.<br />
* a class implementing an interface must not be external<br />
* a ClassType "supports" an interface, if it itself or one of its ancestors implements the interface. It does not automatically support an ancestor of the interface.<br />
* method resolution, procedure IUnknown._AddRef = IncRef;<br />
* delegation: property Name: interface|class read Field|Getter implements AnInterface;<br />
* is-operator:<br />
** IntfVar is IntfType - types must be releated<br />
** IntfVar is ClassType - types can be unrelated, class must not be external<br />
** ObjVar is IntfType - can be unrelated<br />
* as-operator<br />
** IntfVar as IntfType - types must be releated<br />
** IntfVar as ClassType - types can be unrelated, nil returns nil, invalid raises EInvalidCast<br />
** ObjVar as IntfType - can be unrelated, nil if not found, COM: uses _AddRef<br />
* typecast:<br />
** IntfType(IntfVar) - must be related<br />
** ClassType(IntfVar) - can be unrelated, nil if invalid<br />
** IntfType(ObjVar) - nil if not found, COM: if ObjVar has delegate uses _AddRef<br />
** TJSObject(IntfTypeOrVar). Note that you cannot typecast directly to a ''TJSObject'' descendant. You can use ''TJSWindow(TJSObject(IntfType))''.<br />
** jsvalue(intfvar)<br />
* Assign operator:<br />
** IntfVar:=nil;<br />
** IntfVar:=IntfVar2; - IntfVar2 must be same type or a descendant<br />
** IntfVar:=ObjVar; - nil if unsupported<br />
** jsvalue:=IntfVar;<br />
** TGUIDVar:=IntfType;<br />
** TGUIDVar:=IntfVar;<br />
** TGUIDVar:=stringconstant;<br />
** TGUIDStringVar:=IntfVar;<br />
** StringVar:=GuidVar;<br />
* Equal/Inequal operator:<br />
** IntfVar=nil;<br />
** IntfVar=IntfVar2; - must be related<br />
** jsvalue=IntfVar;<br />
** TGUIDVar=IntfType;<br />
** TGUIDVar=IntfVar;<br />
** TGUIDVar=string;<br />
** TGUIDStringVar=IntfVar;<br />
* Passing an COMIntfVar to an untyped parameter or ''JSValue'' does not trigger ''_AddRef, _Release''.<br />
* Assigned(IntfVar)<br />
* RTTI, typeinfo(IntfType), typeinfo(IntfVar)<br />
<br />
Not yet supported: array of intferfacetype, interface as record member.<br />
<br />
==Translating helpers==<br />
<br />
Pas2js supports class helpers, record helpers and type helpers since 1.3. The extend is only virtual, the helped type is kept untouched. <br /><br />
<br />
* A '''class helper''' can "extend" Pascal classes and external JS classes.<br />
* A '''record helper''' can "extend" a record type. In $mode delphi a record helper can extend other types as well, see ''type helper''<br />
* A '''type helper''' can extend all base types like integer, string, char, boolean, double, currency, and user types like enumeration, set, range, array, class, record and interface types. It cannot extend helpers and procedural types.<br /> Type helpers are enabled by default in ''$mode delphi'' and disabled in ''$mode objfpc''. You can enable them with '''{$modeswitch typehelpers}'''.<br />
* By default only one helper is active per type, same as in FPC/Delphi. If there are multiple helpers for the same type, the last helper in scope wins.<br /> A class with ancestors can have one active helper per ancestor type, so multiple helpers can be active, same as FPC/Delphi.<br /> Using '''{$modeswitch multihelpers}''' you can activate all helpers within scope.<br />
* Nested helpers (e.g. ''TDemo.TSub.THelper'') are elevated. Visibility is ignored. Same as FPC/Delphi.<br />
* Helpers cannot be forward defined (e.g. no ''THelper = helper;'').<br />
* Helpers must not have fields.<br />
* '''Class Var, Const, Type'''<br />
* '''Visibility''' : ''strict private .. published''<br />
* '''Function, procedure''': In class and record helpers ''Self'' is the class/record instance. For other types Self is a reference to the passed value.<br />
* '''Class function, class procedure''': Helpers for Pascal classes/records can add ''static'' and non static class functions. Helpers for external classes and other types can only add static class functions.<br />
* '''Constructor'''. Not for external classes. Works similar to construcors, i.e. ''THelpedClass.Create'' creates a new instance, while ''AnObj.Create'' calls the constructor function as normal method. Note that Delphi does not allow calling helper construcors as normal method.<br />
* no destructor<br />
* '''Property''' : getters/setters can refer to members of the helper, its ancestors and the helped class/record.<br />
* '''Class property''' : getter can be static or non static. Delphi/FPC only allows static.<br />
* '''Ancestors''' : Helpers can have an ancestor helper, but they do not have a shared root class, especially not ''TObject''.<br />
* '''no virtual, abstract, override'''. Delphi allows them, but 10.3 crashes when calling.<br />
* '''inherited''' : ''inherited'' inside a method of a class/record calls helper of ancestor.<br />''inherited'' inside a helper depends on the $mode:<br />
** ''$mode objfpc'' : ''inherited;'' and ''inherited Name(args);'' work the same and searches first in HelperForType, then in ancestor(s).<br />
** ''$mode delphi: inherited;'' : skip ancestors and HelperForType, searches first in helper(s) of ancestor of HelperForType.<br />
** ''$mode delphi: inherited name(args);'' : same as $mode objfpc first searches in HelperForType, then Ancestor(s) In any case if ''inherited;'' has no ancestor to call, it is silently ignored, while ''inherited Name;'' gives an error.<br />
* '''RTTI''': ''typeinfo(somehelper)'' returns a pointer to ''TTypeInfoHelper'' with ''Kind tkHelper''.<br />
* There are some special cases when using a '''type helper''' function/procedure on a value:<br />
** ''function result'' : using a temporary variable<br />
** ''const, const argument'' : When helper function tries to assign a value, pas2js raises a EPropReadOnly exception. FPC/Delphi use a temporary variable allowing the write.<br />
** ''property'' : uses only the getter, ignoring the setter. This breaks OOP, as it allows to change fields without calling the setter. This is FPC/Delphi compatible.<br />
** ''with value do ;'' : uses a temporary variable. Delphi/FPC do not support it.<br />
* A method with ''external name'' modifier is treated as an external method of the helped type.<br />
<br />
<br />
<br />
==Translating attributes==<br />
<br />
Attributes are stored in the TTypeInfo objects as streams stored in an array. See the function ''GetRTTIAttributes'' in unit ''TypInfo'' for details.<br />
<br />
==Translating try..finally==<br />
<br />
JavaScript has the same, so it translates straight forward.<br />
<br />
==Translating try..except==<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
Uses SysUtils, Math, JS;<br />
Function DoIt(n: integer): double;<br />
Implementation<br />
Function DoIt(n: integer): double;<br />
var E: Exception;<br />
Begin<br />
try<br />
Result:=double(7.0)/n;<br />
if not IsFinite(Result) then<br />
if n=0 then<br />
raise EZeroDivide.Create<br />
else<br />
raise EOverflow.Create;<br />
except<br />
on EZeroDivide do Result:=0.0;<br />
on E2: EOverflow do Result:=0.0;<br />
else<br />
raise EAbort.Create('Something other: '+String(JS.JSExceptObject));<br />
end;<br />
End;<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","SysUtils"],<br />
function(){<br />
this.DoIt=function(n){<br />
Result = 0;<br />
var E = null;<br />
try{<br />
Result = 7.0 / n;<br />
if (!IsFinite(Result)){<br />
if (n==0){<br />
throw pas.SysUtils.EZeroDivide.$create("Create");<br />
} else {<br />
throw pas.SysUtils.EOverflow.$create("Create");<br />
};<br />
};<br />
}catch($e){<br />
if (pas.SysUtils.EZeroDivide.isPrototypeOf($e)){<br />
Result = 0.0;<br />
} else if (pas.SysUtils.EOverflow.isPrototypeOf($e)){<br />
var E2 = $e;<br />
Result = 0.0;<br />
} else {<br />
throw pas.SysUtils.EAbort.$create("Create",["Something other: "+(""+$e)]);<br />
}<br />
}<br />
return Result;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Division by zero does not raise an exception in JavaScript. Instead it results in Infinity, except for 0/0 which results in NaN.<br />
* There is no ExceptObject in SysUtils.<br />
* When calling external functions keep in mind that JS allows to throw (raise) any value, often a string.<br /> You can access the current except value via JSExceptValue in unit JS.<br /> Note that this is only valid inside the catch-block. The compiler will not warn, if you use it outside.<br />
<br />
<br />
<br />
==Translating enumerators==<br />
<br />
The for..in..do supports enumerating:<br />
<br />
* ordinal types like char, boolean, byte, ..., longword, enums, custom ranges are translated to a for loop.<br />
* set types are translated to a for loop, while const sets and set variables are enumerated via a for(...in...) loop.<br />
* string and array variables are enumerated via for loops.<br />
* for aString in ArrayOfString do ...<br />
* ''for key in jsvalue do'' translates to ''for (key in jsvalue){}''<br />
* ''for key in ExternalClass do''<br /><br />
** <nowiki>If the externalclass has a ''length'' and a matching default property it uses the enumeration of an array. For example </nowiki>''for value in TJSArray do'' enumerates the values of the array, not the index. It checks if the array is nil.<br />
** Otherwise it translates to ''for (key in externalclass){}'', which enumerates the keys (property names) of the JS object.<br />
<br />
The class GetEnumerator function is translated like this:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Unit MyModule;<br />
Interface<br />
<br />
uses Classes;<br />
<br />
procedure DoIt(List: TList);<br />
<br />
Implementation<br />
<br />
procedure DoIt(List: TList);<br />
var<br />
Item: Pointer;<br />
begin<br />
for Item in List do<br />
if Item&lt;&gt;nil then ;<br />
end;<br />
<br />
End.<br />
|<br />
rtl.module("MyModule",<br />
["System","Classes"],<br />
function(){<br />
this.DoIt=function(List){<br />
var Item = null;<br />
var $in1 = List;<br />
try {<br />
while ($in1.MoveNext()) {<br />
Item = $in1.GetCurrent();<br />
if (Item !== null) ;<br />
}<br />
} finally {<br />
$in1 = rtl.freeLoc($in1)<br />
};<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
Notes:<br />
<br />
* Not supported: operator Enumerator, member modifier enumerator (i.e. custom Current and MoveNext)<br />
<br />
<br />
<br />
==Translating function types==<br />
<br />
JavaScript functions work like Delphi's "reference to function", which means like closures, capturing outer variables. Assigning a normal function or nested function to a procedural variable is translated to a simple assignment. A Pascal method needs '''this''' to be the class or class instance.<br /> Note that ''bind'' cannot be used, because it does not support the ''equal'' operator. Instead a wrapper is created:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
type<br />
TMyMethod = procedure(n: integer) of object;<br />
TBird = class<br />
procedure DoIt(n: integer); virtual; abstract;<br />
end;<br />
TMyProc = procedure(n: integer);<br />
procedure DoSome(n: integer);<br />
begin<br />
end;<br />
var<br />
m: TMyMethod;<br />
Bird: TBird;<br />
p: TMyProc;<br />
Begin<br />
m:=@Bird.DoIt;<br />
m(3);<br />
p:=@DoSome;<br />
p(4);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System","UnitA"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod,"TBird",pas.System.TObject,function(){<br />
this.DoIt = function (n) {<br />
};<br />
});<br />
this.DoSome = function (n) {<br />
};<br />
this.m = null;<br />
this.Bird = null;<br />
this.p = null;<br />
$mod.$main = function() {<br />
$mod.m = rtl.createCallback($mod.Bird,"DoIt");<br />
$mod.m(3);<br />
$mod.p = $mod.DoSome;<br />
$mod.p(4);<br />
};<br />
},<br />
[]);<br />
<br />
rtl = {<br />
...<br />
createCallback: function(scope, fn){<br />
var cb;<br />
if (typeof(fn)==='string'){<br />
cb = function(){<br />
return scope[fn].apply(scope,arguments);<br />
};<br />
} else {<br />
cb = function(){<br />
return fn.apply(scope,arguments);<br />
};<br />
};<br />
cb.scope = scope;<br />
cb.fn = fn;<br />
return cb;<br />
},<br />
...<br />
|}<br />
<br />
Notes:<br />
<br />
* You can assign a nested procedure to procedure variable. You don't need and you must not add the FPC "''is nested''" modifier.<br />
* In pas2js a procedural typed declared as '''reference to''' accepts procedures, local procedures and methods. Delphi only supports capturing procedures and methods. FPC 3.0.4 does not support reference-to.<br />
* In pas2js the calling convention ''safecall'' has a special meaning:<br /> Assigning a procedure/method, uses ''rtl.createSafeCallback'' instead of ''createCallback'', enclosing a call in a ''try..catch'' block. When an exception is thrown by JS, it is caught and delegated to ''rtl.handleUncaughtException(err)''.<br /> For example:<br />''aButtonElement.OnClick:=@DoClick;'' uses ''rtl.createSafeCallback''<br />''aButtonElement.OnClick:=SomeElement.OnClick;'' does not.<br /><br />
<br />
<br />
<br />
==Translating anonymous functions==<br />
<br />
Anonymous functions are supported since pas2js 1.1.<br /> Note that in pas2js local procedures are closures as well. See below.<br /> For pas2js 1.0 the next best thing are local procedures. For example:<br />
<br />
{| class="sample"<br />
! Delphi<br />
! Pas2js<br />
|-<br />
|<br />
Program MyModule;<br />
type<br />
TAdder = reference to function(n: integer): integer;<br />
<br />
function CreateAdder(a: integer): TAdder;<br />
begin<br />
Result:=function(b: integer)<br />
begin<br />
Result:=a+b;<br />
end;<br />
end;<br />
<br />
var<br />
Adder: TAdder;<br />
Begin<br />
Adder:=CreateAdder(3);<br />
writeln(Adder(5)); // gives 8<br />
End.<br />
|<br />
Program MyModule;<br />
type<br />
TAdder = reference to function(n: integer): integer;<br />
<br />
function CreateAdder(a: integer): TAdder;<br />
function Add(b: integer): integer;<br />
begin<br />
Result:=a+b;<br />
end;<br />
begin<br />
Result:=@Add;<br />
end;<br />
<br />
var<br />
Adder: TAdder;<br />
Begin<br />
Adder:=CreateAdder(3);<br />
writeln(Adder(5)); // gives 8<br />
End.<br />
|}<br />
<br />
<br />
<br />
==Translating var modifier absolute==<br />
<br />
The absolute modifier works as an alias. That means it works FPC/Delphi compatible for related types like Pointer and TObject, and works incompatible for unrelated types like longword and record (e.g. ''var r: TPoint absolute MyLongInt'').<br /> The modifier is currently only supported for local variables.<br />
<br />
==Translating assert()==<br />
<br />
The Assert(boolean[,string]) function is translated to ''if(bool) throw x''. If unit sysutils is used, it creates an EAssertFailed exception.<br /> Otherwise it throws a string.<br /><br />
<br />
* Command line enable with -Sa, disable with -Sa-<br />
* In code enable with ''{$C+}'' or ''{$Assertions on}'', disable with ''{$C-}'' or ''{$Assertions off}''<br />
<br />
<br />
<br />
==Dispatch messages==<br />
<br />
The procedure modifier '''message''' and the '''Dispatch''' method works similar to FPC/Delphi, as it expects a record of a specific format and '''''TObject.Dispatch''''' calls the corresponding method with that message number or string.<br /> The procedure modifier ''message &lt;integer&gt;'' adds an entry to hidden ''YourClass.$msgint'' object, while the modifier ''message &lt;string&gt;'' adds an entry to the hidden ''YourClass.$msgstr'' object.<br /> Two new directives '''''{$DispatchField fieldname}''''' and '''''{$DispatchStrField fieldname}''''' were added. Insert these directives in front of your class declaration to let the compiler check all methods with message modifiers of this class and its descendants whether they pass a record with the required field. For example:<br />
<br />
<br />
{$DispatchField Msg} // enable checking message methods for record field name "Msg"<br />
{$DispatchStrField MsgStr}<br />
TObject = class<br />
procedure Dispatch(var aMessage); virtual;<br />
procedure DispatchStr(var aMessage); virtual;<br />
end;<br />
TMouseDownMsg = record<br />
Id: integer; // Id instead of Msg, works in FPC, but not in pas2js<br />
x,y: integer;<br />
end;<br />
TMouseUpMsg = record<br />
MsgStr: string;<br />
X,Y: integer;<br />
end;<br />
TWinControl = class<br />
procedure MouseDownMsg(var Msg: TMouseDownMsg); message 3; // warning: Dispatch requires record field Msg<br />
procedure MouseUpMsg(var Msg: TMouseUpMsg); message 'up'; // ok, record with string field name MsgStr<br />
end;<br />
<br />
Note that descendant classes can override the ''$DispatchField'' or disable the check using ''{$DispatchField -}''.<br />
<br />
==Calling JavaScript from Pascal==<br />
<br />
Pas2js allows to write low level functions and/or access a JavaScript library with the following possibilities:<br />
<br />
==The asm block==<br />
<br />
The asm block is pure JavaScript, that is copied directly into the generated .js file.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
var<br />
s: string;<br />
Begin<br />
s = 'Hello World!';<br />
Asm<br />
console.log(s);<br />
End;<br />
End.<br />
|<br />
<nowiki>rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.s = '';<br />
$mod.$main = function(){<br />
$mod.s = "Hello World!";<br />
console.log(s);<br />
};<br />
},<br />
[]);<br />
</nowiki><br />
|}<br />
<br />
Notes:<br />
<br />
* The block is indented to produce more readable JS code. All lines are indented or unindented the same amount, i.e. sub indentation is kept.<br />
* The compiler does neither parse, nor check the syntax of the JS.<br />
* The compiler does not know what Pascal identifiers are used by the asm-block and might remove them, if no Pascal code is using them. To make sure that an identifier is kept, add some dummy code like ''if MyVar=0 then;''<br />
* Accessing an interface, program or library identifier:<br /><br />
** From inside the module you can use ''$mod.Identifier''.<br />
** Otherwise use the fully qualified path ''pas.Unitname.Identifier''.<br />
* Accessing an implementation identifier:<br /><br />
** From inside the unit you can use ''$impl.Identifier''.<br />
** Otherwise use the path ''pas.Unitname.$impl.Identifier''.<br />
* Accessing a class instance member (field, procedure, function, constructor, destructor) from a method of the class: use ''this.Identifier''. Inside a nested function of a method you use the ''Self.Identifier''.<br />
* Accessing a class member (class var, class procedure, class function) from a method of the class: for writing use ''this.$class.Identifier'', for reading you can omit the ''$class''.<br />
* Accessing a class member (class var, class procedure, class function) from a class method of the class: use ''this.Identifier''.<br />
* Access to Properties must use the getter/setter.<br />
* When calling a Pascal method, make sure the '''this''' is correct:<br />
** A class method (e.g. ''class function'', ''class procedure'') needs the class as ''this''.<br />'''Wrong''': ''aCar.DoIt(params,...)''<br />'''Correct''': ''aCar.$class.DoIt(params,...)''<br /><br />
* Calling a Pascal function from a HTML/DOM-element: For example to call a function when user clicks a DOM element you can assign a function to the ''onclick'' property. This will call the function with ''this'' set to the DOM element.<br /> Pascal methods needs a wrapper to set ''this'' to the instance. Examples:<br />
** An unit function: ''DOMElement.onclick = $mod.DoIt;''<br />
** An implementation function: ''DOMElement.onclick = $impl.DoIt;''.<br />
** A method: ''DOMElement.onclick = this.DoIt.bind(this);''<br />
** A class function/procedure: ''DOMElement.onclick = this.DoIt.bind(this.$class);''<br />
** A nested function: ''DOMElement.onclick = DoIt;''.<br />
<br />
<br />
<br />
==The procedure modifier assembler==<br />
<br />
You can write pure JavaScript functions like this:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
<br />
Procedure Log(const s: string); assembler;<br />
Asm<br />
console.log(s);<br />
end;<br />
<br />
Begin<br />
Log('Hello World!');<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.Log = function(s){<br />
console.log(s);<br />
};<br />
$mod.$main = function(){<br />
$mod.Log("Hello World!");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
See also [#asm asm].<br />
<br />
==The procedure modifier external==<br />
<br />
The procedure modifier ''external'' requires a string constant and tells the compiler to replace a reference with this string value. The value is not checked for JS syntax.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
Procedure ConsoleLog(const s: string); external name 'console.log';<br />
// Note: an external procedure has no begin..end block<br />
Begin<br />
ConsoleLog('Hello World!');<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
$mod.$main = function(){<br />
console.log("Hello World!");<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==The procedure modifier varargs==<br />
<br />
Appending the '''varargs''' modifier to a procedure allows to pass arbitrary more parameters to a function. By default these parameters are untyped, i.e. any type fits. Alternatively you can use '''varargs of aType''' to allow only specific types.<br /> To access these arguments use either ''JSArguments'' from unit JS or an ''asm..end'' block.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
uses JS;<br />
function Sum(b: boolean): longint; varargs;<br />
var i: longint;<br />
begin<br />
if b then<br />
asm<br />
for (var i=0; i&lt;arguments.length; i++) Result+=arguments[i];<br />
end<br />
else<br />
for i:=0 to JSArguments.length-1 do<br />
Result:=Result+longint(JSArguments[i]);<br />
end;<br />
var<br />
i: integer;<br />
Begin<br />
i:=Sum(true,2,4,6); // i=12<br />
i:=Sum(false,2,4,6); // i=12<br />
End.<br />
|<br />
rtl.module("program",<br />
["System","JS"],<br />
function(){<br />
var $mod = this;<br />
this.Sum = function(b){<br />
var Result = 0;<br />
var i = 0;<br />
if (b){<br />
for (var i=0; i&lt;arguments.length; i++) Result+=arguments[i];<br />
} else {<br />
for (var $l1 = 1, $le2 = argumens.length; $l1 &lt;= $le2; $l1++){<br />
$i = $l1;<br />
Result = Result + arguments[i];<br />
}<br />
}<br />
return Result;<br />
};<br />
this.i = 0;<br />
$mod.$main = function(){<br />
$mod.i = $mod.Sum(true,2,4,6);<br />
$mod.i = $mod.Sum(false,2,4,6);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
The above example defines a function ''Sum'', that requires the first parameter to be a boolean and then an arbitrary number of parameters. The compiler does not type check the other parameters, so you can pass anything readable.<br />
<br />
==The var modifier external==<br />
<br />
The var modifier ''external'' allows to use a JavaScript variable or constant.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
var<br />
EulersNumber: Double; external name 'Math.E';<br />
d: double;<br />
Begin<br />
d:=EulersNumber;<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
this.d = 0.0;<br />
$mod.$main = function(){<br />
$mod.d = Math.E;<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
<br />
<br />
==The external modifier of class members==<br />
<br />
The method modifier ''external'' works as the procedure modifier, except it uses the scope of the class or instance.<br /> The field modifier ''external'' works as the var modifier, except it uses the scope of the class or instance.<br /> Requires the modeswitch '''externalclass'''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TWrapper = class<br />
private<br />
// let's assume this object has the properties "$Handle", "$id", and "0"<br />
public<br />
Id: NativeInt; external name '$Id';<br />
x: NativeInt; external name '[0]';<br />
y: NativeInt; external name '["A B"]';<br />
function GetState(typ: longint): NativeInt; external name '$Handle.GetState';<br />
procedure DoIt;<br />
end;<br />
procedure TWrapper.DoIt;<br />
begin<br />
Id := GetState(4);<br />
end;<br />
var<br />
W: TWrapper;<br />
Begin<br />
W.Id := 2;<br />
W.x := 3;<br />
W.y := 4;<br />
W.GetState(5);<br />
End.<br />
|<br />
rtl.module("program",<br />
["System"],<br />
function(){<br />
var $mod = this;<br />
rtl.createClass($mod, "TWrapper", pas.System.TObject, function () {<br />
this.DoIt = function(){<br />
this.$Id = this.$Handle.GetState(4);<br />
};<br />
});<br />
this.W = null;<br />
$mod.$main = function(){<br />
$mod.W.$Id = 2;<br />
$mod.W[0] = 3;<br />
$mod.W["A B"] = 4;<br />
$mod.W.$Handle.GetState(5);<br />
};<br />
},<br />
[]);<br />
|}<br />
<br />
* Non identifiers like "0" or "A B" must be enclosed in brackets.<br />
<br />
<br />
<br />
==External classes==<br />
<br />
pas2js introduces a new class modifier "''external name''", which makes the whole class external. External classes allow to easily declare Pascal wrappers for JavaScript objects and function objects.<br /> They need the modeswitch '''externalclass''' in front of the class.<br /> An external class is not a TObject and has none of its methods.<br /> All members are external. If you omit the ''external'' modifier the external name is the member name. Keep in mind that JS is case sensitive.<br /> Properties work the same as with Pascal classes, i.e. are replaced by Getter/Setter.<br /> Destructors are not allowed.<br /> Constructors are supported in four ways:<br />
<br />
* ''constructor New'' is translated to ''new ExtClass(params)'', and for nested external class: ''new ExtParentClass.ExtClass(Params)''<br />
* ''<nowiki>constructor New; external name ''GlobalFunc''</nowiki>'' is translated to ''new GlobalFunc(params)''.<br />
* ''constructor SomeName; external name '''{}' is translated to ''{}''.<br />
* Otherwise it is translated to ''new ExtClass.FuncName(params)'', and for nested external class: ''new ExtParentClass.ExtClass.FuncName(params)''.<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TJSDate = class external name 'Date'<br />
private<br />
function getYear: NativeInt;<br />
procedure setYear(const AValue: NativeInt);<br />
public<br />
constructor New;<br />
constructor New(const MilliSecsSince1970: NativeInt);<br />
class function now: NativeInt;<br />
property Year: NativeInt read getYear write setYear;<br />
end;<br />
var<br />
d: TJSDate;<br />
Begin<br />
d:=TJSDate.New;<br />
d.Year:=d.Year+1;<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
this.d = null;<br />
$mod.$main = function () {<br />
$mod.d = new Date();<br />
$mod.d.setYear($mod.d.getYear() + 1);<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* An external class can descend from another external class.<br />
* Any class instance can be type casted to any root class.<br />
* A Pascal class can descend from an external class.<br />
* You can define a class-of external class and the '''is''' and '''as''' operators work similar.<br />
* Class variables work as in JavaScript. That means, each descendant and each instance can have its own value. For example ''TExtA.Value'' might be different from ''InstanceExtA.Value''. Setting ''InstanceExtA.Value'' does not change ''TExtA.Value''.<br />
* Const with an expression are replaced by the expression.<br />
* Const without an expression are treated as a readonly variable.<br />
* Class functions and class procedures are allowed, but can only be called via the class, not via an instance.<br /> For example you can call the class function ''TJSString.fromCharCode()'', but you cannot call ''aJSString.fromCharCode()''.<br />
* Since class types are JS objects it is possible to typecast a class type to the JS Object, e.g. ''TJSObject(TObject)''. Note that you cannot typecast directly to a ''TJSObject'' descendant in $mode objfpc. You can use ''TJSWindow(TJSObject(ExtClassInstance))''.<br />
* You can typecast function addresses and function references to JS function, e.g. ''TJSFunction(@SomeProc)'', ''TJSFunction(OnClick)''. Keep in mind that typecasting a method address creates a function wrapper to bind the Self argument, except when typecasting to ''TJSFunction'' (pas2js 1.5+).<br />
<br />
==External class as ancestor==<br />
<br />
A Pascal class can descend from an external class - a JS object or function.<br /> The methods ''AfterConstruction'' and ''BeforeDestruction'' are called if they exist.<br /> New instances of a JS Object descendant are created by default with ''Object.create(ancestorclass)''.<br /> New instances of a JS Function descendant are created by default with ''new DescendantFunc()''.<br /> You can override this, by providing a non private <br />'''class function NewInstance(fnname: string; const paramsarray): TPasClass; virtual;'''. This method is called to create a new instance and before calling the constructor. The name is arbitrary, but the function must be the '''first non private''', non external, virtual class function with the class as result type.<br /><br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
<br />
// Example for descending a Pascal class from a JS Object<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TExtA = class external name 'ExtA'<br />
end;<br />
TMyB = class(TExtA)<br />
protected<br />
// optional: override default allocation<br />
class function NewInstance(fnname: string; const paramarray): TMyB; virtual;<br />
end;<br />
class function TMyB.NewInstance(fnname: string; const paramarray): TMyB;<br />
Begin<br />
asm<br />
Result = Object.create(ExtA); // that is what the rtl does<br />
end;<br />
End;<br />
<br />
Begin<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
rtl.createClassExt($mod, "TMyB", ExtA, "NewInstance", function () {<br />
this.$init = function () {<br />
};<br />
this.$final = function () {<br />
};<br />
this.NewInstance = function (fnname, paramarray) {<br />
var Result = null;<br />
Result = Object.create(ExtA);<br />
return Result;<br />
};<br />
});<br />
$mod.$main = function () {<br />
};<br />
});<br />
|}<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
<br />
// Example for descending a Pascal class from a JS Function<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
uses JS;<br />
type<br />
TExternalFunc = class external name 'ExternalFunc'(TJSFunction)<br />
constructor New(a: word);<br />
end;<br />
TMyFunc = class(TExternalFunc)<br />
constructor Create(b: word);<br />
end;<br />
constructor TMyFunc.Create(b: word);<br />
Begin<br />
inherited New(b+1); // optional: call inherited constructor function<br />
End;<br />
<br />
var<br />
f: TMyFunc;<br />
Begin<br />
f:=TMyFunc.Create(3);<br />
writeln(jsInstanceOf(f,TExternalFunc)); // writes true, instanceof operator works as expected<br />
End.<br />
|<br />
rtl.module("program",["System","js"],function () {<br />
var $mod = this;<br />
rtl.createClassExt($mod, "TMyFunc", ExternalFunc, "", function () {<br />
this.$init = function () {<br />
};<br />
this.$final = function () {<br />
};<br />
this.Create$2 = function (b) {<br />
this.$ancestorfunc(b+1);<br />
};<br />
});<br />
this.f = null;<br />
$mod.$main = function () {<br />
f = $mod.TMyFunc.$create("Create$2",[3]);<br />
pas.System.Writeln(pas.JS.jsInstanceOf(f,ExternalFunc));<br />
};<br />
});<br />
|}<br />
<br />
==The JSValue type==<br />
<br />
Pas2js introduces a new type '''JSValue''', which works similar to a JS variable. You can assign almost any value to it and it can be type casted to many types. JSValue is useful for JS wrappers, when a variable can have multiple types. And it can be used for containers storing arbitrary data, e.g. a list of JSValue.<br /> Key features:<br /><br />
<br />
* A JSValue variable initial value is undefined.<br />
* Operators: =, &lt;&gt;<br />
* type casting a ''JSValue'' to ...<br />
** ''Integer: Math.floor(aJSValue)'' Note: may return ''NaN''<br />
** ''Boolean: !(aJSValue == false)'' Note: works for numbers too, ''0==false''<br />
** ''Double: rtl.getNumber(aJSValue)'' Note: ''typeof(n)=="number"?n:NaN;''<br />
** ''String: ""+aJSValue''<br />
** ''Char: rtl.getChar(aJSValue)'' Note: ''((typeof(c)!="string") &amp;&amp; (c.length==1)) ? c : ""''<br />
** class instance or class-of: ''rtl.getObject()'' Note: checks for type ''"object"''<br />
** enum type<br />
** pointer<br />
* A JSValue in a conditional expressions ''If aJSValue then, while aJSValue do, repeat until aJSValue'' has the same meaning as in JS: the condition is true, if the value is not ''<nowiki>undefined, false, null, NaN, 0, ''</nowiki>''. Note that ''new Boolean(false)'' is not ''null'' and the condition is true.<br />
* ''function Assigned(V: jsvalue): boolean'' returns true if<br />''(V!=undefined) &amp;&amp; (V!=null) &amp;&amp; (!rtl.isArray(V) || (V.length &gt; 0))''<br />
* ''function StrictEqual(const A: jsvalue; const B): boolean''<br />
* ''function StrictInequal(const A: jsvalue; const B): boolean''<br />
* Any array can be assigned to an ''array of jsvalue''.<br />
* is-operator: ''jsvalue is class-type'', ''jsvalue is class-of-type''<br /><br />
* The unit JS provides many utility functions for JSValue, like ''hasString, hasValue, isBoolean, isNumber, isInteger, isObject, isClass, isClassInstance, etc..''<br />
<br />
<br />
<br />
==Accessing JS object properties with the bracket accessor==<br />
<br />
Pas2js allows to define index properties that map directly to the JS object properties. For example the default property of TJSObject allows to get and set the properties of an object. For example ''TJSObject(AnObject)['Name']:=Value;''<br /> Another example is the default property of TJSArray, that allows access via integers ''aTJSArray[3]:=Value;''<br /> To define your own bracket accessor define a normal index property and define the getter/setter as ''external name '[]'''.<br /> Here is an example for a read only accessor:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
{$modeswitch externalclass}<br />
type<br />
TExtA = class external name 'ExtA'<br />
private<br />
function GetItems(Index: integer): String; external name '[]';<br />
public<br />
property Items[Index: integer]: String read GetItems; default;<br />
end;<br />
var<br />
Obj: TExtA;<br />
s: String;<br />
Begin<br />
... get Obj from somewhere ...<br />
s:=Obj[2];<br />
End.<br />
|<br />
rtl.module("program",["System"],function () {<br />
var $mod = this;<br />
this.Obj = undefined;<br />
this.s = "";<br />
$mod.$main = function () {<br />
$mod.s = Obj[2];<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* A property can have a mix of normal accessor and bracket accessor. For example a bracket accessor as getter and a normal function as setter.<br />
<br />
<br />
<br />
==RTTI - Run Time Type Information==<br />
<br />
The RTTI provides access to the type data of all published properties, fields and methods. The type data provides similar information as Delphi/FPC, but the internals are very different. Delphi/FPC uses pointers, variant records and fake static arrays, which have no equivalent in JS. Instead pas2js uses external classes. For example:<br />
<br />
<br />
TTypeInfo = class external name 'rtl.tTypeInfo'<br />
public<br />
Name: String external name 'name';<br />
Kind: TTypeKind external name 'kind';<br />
end;<br />
TTypeInfoClass = class of TTypeInfo;<br />
<br />
TTypeInfoInteger = class external name 'rtl.tTypeInfoInteger'(TTypeInfo)<br />
public<br />
MinValue: NativeInt external name 'minvalue';<br />
MaxValue: NativeInt external name 'maxvalue';<br />
OrdType : TOrdType external name 'ordtype';<br />
end;<br />
<br />
<br />
The '''typeinfo''' function works on type, var, const and property identifiers. By default it returns a ''pointer''. If the typinfo unit is used it returns the appropiate ''TTypeInfo''. For instance ''typeinfo(integer)'' returns a ''TTypeInfoInteger''.<br />''Typeinfo'' of a ''var'' or ''const'' returns the typeinfo of its type, not of its current runtime value. The exception is a class and class-of instance variable (e.g. ''var o: TObject; ... typeinfo(o)''), which returns the typeinfo of the current runtime value. If ''o'' is ''nil'' it will give a JS error.<br /> Local types (i.e. inside a procedure) do not have typeinfo.<br /> Open array parameters are not yet supported.<br /> Note that FPC ''typeinfo(aClassVar)'' returns the compiletime type, so it works on ''nil''.<br /><br />
<br />
==Async/AWait==<br />
<br />
Pas2js supports the JS operators async and await to simplify the use of Promise. The await operator corresponds to three intrinsic Pas2js functions:<br />
<br />
* ''function await(AsyncFunctionWithResultT()): T;'' // implicit promise, the inner () can be omitted<br />
* ''function await(aType; p: TJSPromise): aType;'' // explicit promise requires the resolved type<br />
* ''function await(aType; j: jsvalue): aType;'' // explicit promise requires the resolved type<br />
<br />
The await function can only be used inside a procedure with the async modifier.<br /> Example for the explicit promise:<br />
<br />
{| class="sample"<br />
! Pascal<br />
! JavaScript<br />
|-<br />
|<br />
Program MyModule;<br />
<br />
uses JS, Web;<br />
<br />
function ResolveAfter2Seconds: TJSPromise;<br />
begin<br />
Result:=TJSPromise.new(procedure(resolve, reject : TJSPromiseResolver)<br />
begin<br />
window.setTimeout(procedure<br />
begin<br />
resolve('resolved');<br />
end,<br />
2000); // wait 2 seconds<br />
end);<br />
end;<br />
<br />
procedure AsyncCall; async;<br />
var s: string;<br />
begin<br />
writeln('calling');<br />
s := await(string,resolveAfter2Seconds()); // does not check if result is really a string<br />
writeln(s); // expected output: 'resolved'<br />
end;<br />
<br />
begin<br />
AsyncCall;<br />
end.<br />
|<br />
rtl.module("program",["System","JS","Web"],function () {<br />
"use strict";<br />
var $mod = this;<br />
this.ResolveAfter2Seconds = function () {<br />
var Result = null;<br />
Result = new Promise(function (resolve, reject) {<br />
window.setTimeout(function () {<br />
resolve("resolved");<br />
},2000);<br />
});<br />
return Result;<br />
};<br />
this.AsyncCall = async function () {<br />
var s = "";<br />
pas.System.Writeln("calling");<br />
s = await $mod.ResolveAfter2Seconds();<br />
pas.System.Writeln(s);<br />
};<br />
$mod.$main = function () {<br />
$mod.AsyncCall();<br />
};<br />
});<br />
|}<br />
<br />
Notes:<br />
<br />
* The await function does only compile time checks, no runtime checks.<br />
* Inside an async function/procedure you can pass a ''TJSPromise'' to the ''exit()'' function. For example:<br />''exit(aPromise);''<br />''exit(inherited);''<br />
<br />
<br />
<br />
==Compiler directives==<br />
<br />
In config files:<br />
<br />
* <nowiki>#IFDEF macroname</nowiki><br />
* #IFNDEF macroname<br />
* #IF expression - same as $if, except only defines<br />
* #ELSEIF<br />
* #ELSE<br />
* #ENDIF<br />
* #ERROR text<br />
<br />
In source files:<br />
<br />
* <span class="tt">{$Define ''MacroName''}</span>: defines macro ''MacroName'' with value '1'.<br />
* <span class="tt">{$Define ''MacroName:=value''}</span>: defines macro ''MacroName'' with custom value.<br />
* <span class="tt">{$Undef ''MacroName''}</span>: undefines macro ''MacroName''.<br />
* <span class="tt">{$IfDef ''MacroName''}</span>: if ''MacroName'' is not defined, skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br />
* <span class="tt">{$IfNDef ''MacroName''}</span>: as <span class="tt">$IfDef</span>, except negated.<br />
* <span class="tt">{$If ''boolean expression''}</span>: if ''expression'' evaluates to true (not '0'), skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br /> Supported functions and operators:<br /><br />
** macro - replaced by its value, a simple define has value '1'<br />
** defined(macro) - '1' if defined, '0' otherwise<br />
** undefined(macro) - as <span class="tt">''not defined(macro)''</span><br />
** option(letter) - same as ''<span class="tt">{$IFOpt letter+}</span>''<br />
** <span class="tt">not</span> - first level of precedence<br />
** <span class="tt">*, /, div, mod, and, shl, shr</span> - second level of precedence<br />
** <span class="tt">+, -, or, xor</span> - third level of precedence<br />
** <span class="tt">=, &lt;&gt;, &lt;, &gt;, &lt;=, &gt;=</span> - fourth level of precedence<br />
** If the operands can be converted to numbers they are combined as numbers, otherwise as strings. Not supported functions and operators:<br /><br />
** <span class="tt">defined(Pascal identifier), undefined(Pascal identifier)</span><br />
** <span class="tt">declared(Pascal identifier)</span><br />
** <span class="tt">in</span> operator<br />
* <span class="tt">{$IfOpt ''Letter+,-''}</span>: if ''expression'' evaluates to true (not '0'), skip to next <span class="tt">$Else</span> or <span class="tt">$EndIf</span>. Can be nested.<br />
* <span class="tt">{$Else}</span>: If previous <span class="tt">$IfDef</span>, <span class="tt">$If</span> or <span class="tt">$IfOpt</span> was skipped, execute next block, otherwise skip.<br />
* <span class="tt">{$ElseIf ''boolean expression''}</span> : As <span class="tt">$Else</span>, except with an extra expression like <span class="tt">$if</span> to test. There can be multiple <span class="tt">$elseif</span>.<br />
* <span class="tt">{$EndIf}</span> : ends an <span class="tt">$IfDef</span> block<br />
* <span class="tt">{$mode delphi} or {$mode objfpc}</span> : Same as <span class="tt">-Mdelphi</span> or <span class="tt">-Mobjfpc</span>, but only for this unit. You can use units of both modes in a program. If present must be at the top of the unit, or after the module name.<br />
* <span class="tt">{$modeswitch externalclass}</span> : allow declaring external classes<br />
* <span class="tt">{$modeswitch arrayoperators}</span> : allow + operator to concatenate arrays, default in mode delphi<br />
* <span class="tt">{$modeswitch OmitRTTI}</span> : treat published sections as public<br />
* <span class="tt">{$macro on|off}</span> enables macro replacements. Only macros with a value are replaced. Macros are never replaced inside directives.<br />
* <span class="tt">{$I filename} or {$include filename}</span> - insert include file<br />
* <span class="tt">{$I %param%}</span> :<br />
** <span class="tt">%date%</span>: current date as string literal, '[yyyy/mm/dd]'<br />
** <span class="tt">%time%</span>: current time as string literal, 'hh:mm:ss'. Note that the inclusion of <span class="tt">%date%</span> and <span class="tt">%time%</span> will not cause the compiler to recompile the unit every time it is used: the date and time will be the date and time when the unit was last compiled.<br />
** <span class="tt">%file%</span>: current source filename as string literal, e.g. '''unit1.pas'''<br />
** <span class="tt">%line%</span>: current source line number as string literal, e.g. '''123'''<br />
** <span class="tt">%linenum%</span>: current source line number as integer, e.g. ''123''<br />
** <span class="tt">%currentroutine%</span>: name of current routine as string literal<br />
** <span class="tt">%pas2jstarget%, %pas2jstargetos%, %fpctarget%, %fpctargetos%</span>: target os as string literal, e.g. 'Browser'<br />
** <span class="tt">%pas2jstargetcpu%, %fpctargetcpu%</span>: target cpu as string literal, e.g. 'ECMAScript5'<br />
** <span class="tt">%pas2jsversion%, %fpcversion%</span>: compiler version as strnig literal, e.g. '1.0.2'<br />
** If param is none of the above it will use the environment variable. Keep in mind that depending on the platform the name may be case sensitive. If there is no such variable an empty string ''<nowiki>''</nowiki>'' is inserted.<br />
* <span class="tt">{$Warnings on|off}</span><br />
* <span class="tt">{$Notes on|off}</span><br />
* <span class="tt">{$Hints on|off}</span><br />
* <span class="tt">{$Error text}</span> : emit an error<br />
* <span class="tt">{$Warning text}</span> : emit a warning<br />
* <span class="tt">{$Note text}</span> : emit a note<br />
* <span class="tt">{$Hint text}</span> : emit a hint<br />
* <span class="tt">{$Message hint-text}</span> : emit a hint<br />
* <span class="tt">{$Message hint|note|warn|error|fatal text}</span> : emit a message<br />
* <span class="tt">{$Warn identifier on|off|default|error}</span> : enable or disable a specific hint.<br /> Note, that some hints like "Parameter %s not used" are currently using the enable state at the end of the module, not the state at the hint source position.<br /> Identifier can be a message number as written by -vq or one of the following case insensitive:<br /><br />
** <span class="tt">CONSTRUCTING_ABSTRACT</span> : Constructing an instance of a class with abstract methods.<br />
** <span class="tt">IMPLICIT_VARIANTS</span> : Implicit use of the variants unit.<br />
** <span class="tt">NO_RETVAL</span> : Function result is not set<br />
** <span class="tt">SYMBOL_DEPRECATED</span> : Deprecated symbol.<br />
** <span class="tt">SYMBOL_EXPERIMENTAL</span> : Experimental symbol<br />
** <span class="tt">SYMBOL_LIBRARY</span><br />
** <span class="tt">SYMBOL_PLATFORM</span> : Platform-dependent symbol.<br />
** <span class="tt">SYMBOL_UNIMPLEMENTED</span> : Unimplemented symbol.<br />
** <span class="tt">HIDDEN_VIRTUAL</span> : method hides virtual method of ancestor<br />
** <span class="tt">GARBAGE</span> : text after final end.<br />
** <span class="tt">BOUNDS_ERROR</span> : range check errors<br />
** <span class="tt">MESSAGE_DIRECTIVE</span> : user defined $message<br />
* <span class="tt">{$M+}, {$TypeInfo on}</span> : switches default visibility for class members from public to published<br />
* <span class="tt">{$ScopedEnums on|off}</span> disabled(default): propagate enums to global scope, enable: needs fqn e.g. TEnumType.EnumValue.<br />
* <span class="tt">{$C+}</span> generate code for assertions<br />
* <span class="tt">{$H+}</span> , but not <span class="tt">{$H-}</span><br />
* <span class="tt">{$J-}, {$WriteableConst off}</span> : Typed const become readonly. For example ''const i:byte=3; ... i:=4'' creates a compile time error.<br />
* <span class="tt">{$M+}</span> : allow published members<br />
* <span class="tt">{$Q+}</span> : not yet supported, ignored<br />
* <span class="tt">{$R+}, {$RangeChecks on}</span> : compile time range check hints become errors and add runtime range checks for assignments.<br />
* <span class="tt">{$ObjectChecks on|off}</span> :<br />
** Verify method calls, i.e. check at runtime in every method if ''Self'' is a descendant class.<br />
** Check type casts, e.g. ''TBird(AnObject)'' becomes ''AnObject as TBird''<br />
* <span class="tt">{$DispatchField Msg}</span> : enable checking ''message number'' methods for record field name "Msg"<br />
* <span class="tt">{$DispatchStrField MsgStr}</span> : enable checking ''message string'' methods for record field name "Msg"<br />
* <span class="tt">{$Optimization Name}</span> : same as command line option -OoName<br />
* <span class="tt">{$linklib filename [alias]}</span>:<br />
** <span class="tt">filename</span> filename to import.<br />
** <span class="tt">alias</span> alias for this import. Must be a pascal identifier. If no alias is specified, the last part of the filename is used, all non-alphanumeric characters are converted to underscores.<br />
<br />
Defines:<br />
<br />
* PASJS<br />
* PAS2JS_FULLVERSION - major*1000+minor*100+release, e.g. 1.2.3 = 10203<br />
* Target platform: Browser, NodeJS, Pas2JSTargetOS=&lt;value&gt;<br />
* Target processor: ECMAScript5, ECMAScript6, ECMAScript=5, Pas2JSTargetCPU=&lt;value&gt;<br />
* Mode: DELPHI, OBJFPC<br />
<br />
<br />
<br />
==Numbers==<br />
<br />
JavaScript only supports double. All Pascal number types and enum values are mapped to this. A double supports integers from<br /> MinInteger = -$10000000000000;<br /> MaxInteger = $fffffffffffff;<br /> MinDouble = 5.0e-324;<br /> MaxDouble = 1.7e+308;<br /><br /> Intrinsic integer types:<br />
<br />
* Byte - unsigned 8-bit<br />
* ShortInt - signed 8-bit<br />
* Word - unsigned 16-bit<br />
* SmallInt - signed 16-bit<br />
* LongWord - unsigned 32-bit<br />
* LongInt - signed 32-bit<br />
* NativeUInt - unsigned 53-bit<br />
* NativeInt - signed 54-bit<br />
<br />
Notes:<br />
<br />
* Division by zero does not raise an exception. 0/0 results in NaN, positive/0 is Infinity, negative/0 is -Infinity.<br />
* NaN&lt;&gt;NaN<br />
* Overflows work differently. For example in Delphi adding 100 to a byte of 200 gives ''300 and $ff = 44'', while in pas2js it gives 300, which is not a byte anymore.<br />
* Math.isNan(double) tests for NaN. Otherwise false. isNan(Infinity)=false.<br />
* Math.isFinite(double) tests if not NaN, positive or negative infinity.<br />
* Math.isInfinite(double) tests if positive or negative infinity.<br />
* For more functions see unit Math.<br />
* To make porting easier Single is defined in the system unit as alias of double, but gives a warning. Since using higher precision might give unexpected results you should check every place.<br />
<br />
<br />
<br />
==Other supported Pascal elements==<br />
<br />
* '''break''', '''continue''', '''exit''', '''exit()'''<br />
* '''chr''', '''ord'''<br />
* alias type and type alias type<br />
* ''inc()'' and ''dec()'' to += -=<br />
* Converts "a div b" to "Math.floor(a / b)"<br />
* and, or, xor, not: logical and bitwise<br />
* Name conflicts with JS identifiers are automatically fixed by changing case. For example a Pascal function "''apply"'' is renamed to "''Apply''".<br />
* uses unitname in 'filename'. In ''$mode delphi'' the in-filenames are only allowed in the program and the unitname must fit the filename, e.g. ''uses unit1 in 'sub/Unit1.pas''.<br /> In ''$mode objfpc'' units can use in-filenames too and alias are allowed, e.g. ''uses foo in 'bar.pas'''.<br />
* The intrinsic procedure '''str''' works with boolean, integer, float and enumvalue.<br /> Additionally there is '''str''' function, that takes an arbitrary number of arguments and returns a concatenated string. It supports string as parameter too. For example s:=str(i,' ',d:1:5).<br /> Width and precision is supported. str(i:10) will add spaces to the left to fill up to 10 characters. str(aDouble:1:5) returns a string in decimal format with 5 digits for the fraction.<br />
* Intrinsic procedure WriteStr(out s: string; params...)<br />
* ''Debugger;'' converts to ''debugger;''. If a debugger is running it will break on this line just like a break point.<br />
* ''function concat(string1,string2,...): string'' since 1.3<br />
* ''$mode delphi: function lo|hi(integer): byte'' since 1.3<br />
* ''$mode objfpc: function lo|hi(integer): byte|word|longword'' since 1.3<br />
* ''function GetTypeKind(Type or Var): TTypeKind;'' since 1.5<br />
<br />
==Not supported elements==<br />
<br />
* Class destructor<br />
* Enums with custom values<br />
* Global properties<br />
* Futures<br />
* Inline<br />
* Objects<br />
* Operator overloading<br />
* Pointer arithmetic<br />
* Package<br />
* Resources<br />
* RTTI extended, $RTTI<br />
* Variant records<br />
* Variants<br />
<br />
==JavaScript Version==<br />
<br />
Code generation depending on -P option:<br />
<br />
* ECMAScript5<br />
* ECMAScript6: using 0b for binary literals, and 0o for octal literals<br />
<br />
<br />
<br />
==Creating source maps==<br />
<br />
Source maps are files telling the browser what JavaScript comes from which original source (e.g. Pascal file), similar to debug information in FPC/Delphi.<br /> In 2017 FireFox and Chrome supports source maps.<br /> You can enable generating source map files by using the ''-Jm'' option.<br /> The compiler generates one module.js.map file for every generated module.js file. The last line of the .js file contains the line<br />''//# sourceMappingURL=module.js.map''<br /> telling the browser where to find the source map.<br /> The source map contains references to the Pascal files and included .js files (e.g. -Jirtl.js) relative to the location of the source map. Note that if the Pascal file lies in a parent directory, the relativ path contains '../'. You can change the base directory of the relative paths by using the option ''-Jmbasedir=&lt;x&gt;''. For example ''-JmC:\www\pas'' creates paths relative to C:\www\pas.<br /> You can set the base URL, where the browser finds the Pascal sources, by passing the ''-Jmsourceroot=&lt;x&gt;'' option. For example ''-Jmsourceroot=http://www.yoursite.com/pas/''. The browser prepends this to the source map filenames when downloading the original source files (e.g. the .pas files).<br /> You can include the whole Pascal sources in the source map using the option ''-Jminclude''.<br /><br /> To show the generated mapping for each line you can use the tool fpc/packages/fcl-js/examples/srcmapdump.<br /><br />
<br />
* Option -JmXSSIHeader: According to the specifications sourcemap should start with the XSSI (cross site script inclusion) protection header '')]}'''. If your browser does not support that, disable it with ''-JmXSSIHeader-''. See here the specs: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.h7yy76c5il9v<br />
[[Category:Pas2js]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Brook_Framework&diff=153498Brook Framework2022-08-16T15:53:12Z<p>Skalogryz: /* Alternatives */</p>
<hr />
<div>= About =<br />
<br />
'''Brook Framework''' is a cross-platform microframework which helps to develop web Pascal applications built by Delphi or Lazarus IDE and Free Pascal. Its core has been developed using [https://risoflora.github.io/libsagui libsagui], a cross-platform C library incorporating GNU libmicrohttpd, uthash, PCRE2, ZLib and GnuTLS.<br />
<br />
Author: Silvio Clecio<br />
<br />
License: GNU LGPL<br />
<br />
= Homepage =<br />
<br />
* Get started, documentation, license, download and others details: [https://risoflora.github.io/brookframework Home page].<br />
* [https://github.com/risoflora/brookframework GitHub repository]<br />
<br />
= Comparison with Brook for Free Pascal =<br />
<br />
[[Brook for Free Pascal]] is an earlier web application library by the same developer.<br />
<br />
* '''Brook Framework''' uses libmicrohttpd and GnuTLS, wrapped into libsagui, for its underlying HTTP/S functionality;<br />
* '''Brook for Free Pascal''' is pure Pascal and relies on the HTTP functionality provided by fcl-web, covering CGI, FastCGI and standalone.<br />
<br />
For deployment of Brook Framework applications, it is necessary to bundle the application with the libsagui DLL/dylib/so file; depending on how libsagui is built, it may be necessary to also bundle other dynamic library files that libsagui depends on.<br />
<br />
= Alternatives =<br />
<br />
* [https://github.com/synopse/mORMot mORMot] - Synopse mORMot ORM/SOA/MVC framework.<br />
* [https://github.com/motaz/freespider FreeSpider] - Web development package for Free Pascal/Lazarus.<br />
* [http://wiki.freepascal.org/fcl-web FCL-Web] Built-in Free Pascal web library.<br />
* [https://fanoframework.github.io Fano Framework] Web application framework for modern Pascal programming language.<br />
[[Category:Components]]<br />
[[Category:Networking]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=WebPageDevelopment&diff=153492WebPageDevelopment2022-08-16T15:36:51Z<p>Skalogryz: </p>
<hr />
<div>The main web page of Lazarus project can be found here :<br />
http://www.lazarus-ide.org/<br />
<br />
It is done with HTML and PHP, and connects to SMF (Simple Machines) forum SW.<br />
<br />
The sources are in a subversion repository :<br />
https://svn.freepascal.org/svn/lazaruswebsite/<br />
<br />
which is almost the same address that Lazarus sources have, only "lazarus" is replaced with "lazaruswebsite".<br />
It is readable by anyone.<br />
<br />
Testing the page locally requires normal web server + PHP installation.<br />
SMF (Simple Machines) does not need to be installed though.<br />
The PHP code is clever enough to insert "dummy" forum data when real<br />
data is not available.<br />
<br />
The page can be improved by anyone by sending patches through bug tracker, the same way that patches are sent for pascal sources.<br />
However, if the changes are clearly a matter of taste (like colors) then please discuss the changes first in mailing list or forum.<br />
<br />
When a patch is applied to SVN, it is updated automatically to a test server :<br />
http://test.lazarus-ide.org/<br />
After it is checked for visual appearance and for potential security issues, it will be updated manually to the "production" server.<br />
<br />
However the test server is not very useful for a contributor because the changes must be tested locally anyway.<br />
<br />
[[Category:Lazarus internals]]<br />
[[Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_Team_-_Git_Workflows&diff=153491Lazarus Team - Git Workflows2022-08-16T15:36:42Z<p>Skalogryz: /* Issue Tracker */</p>
<hr />
<div>This page contains workflows for Lazarus team members, when working with GIT.<br />
<br />
= git pull --rebase =<br />
<br />
;Commits made by team members should have a linear history:<br />
<br />
If you have local commits to push, and the remote branch has new commits too, then use '''rebase'''.<br />
Do not create any merge commits.<br />
<br />
git pull --rebase<br />
or<br />
git rebase<br />
<br />
= Merge Requests / Pull Requests =<br />
<br />
Merge requests should be included by creating a merge commit<br />
<br />
git merge merge-request/foo<br />
<br />
{{Note| To pull merger request to your local git: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#checkout-locally-by-modifying-gitconfig-for-a-given-repository }}<br />
<br />
= Cherry picking / Merging =<br />
<br />
Do not "squash" commits when cherry picking.<br />
<br />
If commits are squashed, then [https://git-scm.com/docs/git-cherry "git cherry"] wont work.<br />
<br />
Use<br />
git cherry pick -x<br />
The "-x" adds "cherry picked from commit"<br />
<br />
= ".gitattributes" - Adding new file extensions =<br />
<br />
{{Warning| Adding new extensions can have unwanted side effects on existing file. }}<br />
<br />
If you add files with an new extension (not yet covered by .gitattributes) then it is best to add that extension (or the concrete file)<br />
<br />
Files not covered by .gitattributes will be left to git figuring out on its own, if they are text. <br />
Since this affects line endings, it is best to tell git, what we want the file to be.<br />
<br />
<b><u>After editing .gitattributes you must run (before committing): </u></b><br />
git add --renormalize .<br />
<br />
Then check '''git status''' which files were affected.<br />
<br />
= Issue Tracker =<br />
<br />
== Milestone ==<br />
<br />
When closing an issue, ensure to set the correct milestone.<br />
<br />
If the issues is (or will be) merged to a fixes branch, set the milestone for the fixes release.<br />
(If the merge later fails, the milestone must be amended).<br />
<br />
If you do not merge (cherry pick) yourself:<br />
* leave the issue open<br />
* Set the fixes milestone<br />
* add the label<br />
:: merge::merge-and-close<br />
<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Revision control]]<br />
[[Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_release_engineering&diff=153490Lazarus release engineering2022-08-16T15:36:34Z<p>Skalogryz: /* Related pages */</p>
<hr />
<div><br />
{{Note|This page is still work in progress. For feedback please use our mailing list.}}<br />
<br />
== Checklist ==<br />
<br />
<table width="100%"><tr valign="top"><br />
<td style="min-width:40%"><br />
<br />
=== Major Release ===<br />
<br />
* Decide on new release<br />
** Check internally with the team <br />
** Decide on approx Date for first RC (date may change). This may be optional.<br />
* Prepare<br />
** Create fixes branch in SVN<br />
** Create fixes page on wiki<br />
* Announce<br />
** <u>Special announcement</u> for translators.<br />
** <u>Public announcement</u> with approx RC date (if available).<br />
<br />
=== For each Release Candidate ===<br />
* Between 1 and 2 weeks before RC is due<br />
** Check internally with the team <br />
** <u>Public reminder/announcement</u> that RC is due.<br />
** If needed: Reschedule/Delay<br />
* Publish the Release Candidate<br />
** Tag RC in SVN<br />
** Build and upload<br />
** <u>Public announcement</u> of RC<br />
<br />
Typical life time of an RC can be expected to be between 3 and 6 weeks. But in the end will be decided at the discretion of the team.<br />
<br />
=== For the Release ===<br />
* At least 2 weeks before Release<br />
** Check internally with the team <br />
** Public reminder/announcement that Release is due. No more RC planned.<br />
** Repeat <u>Special announcement</u> for translators.<br />
* 1 week before Release - Depending on feedback (the team should have 3 or 4 days time to discuss this):<br />
*** keep decision to release<br />
*** move date of release<br />
*** schedule another RC<br />
** 3 to 5 days later: <u>Public announcement</u> of above decision, if a change to the plan was made. <br />
* Make the Release<br />
** Tag RC in SVN<br />
** Build and upload<br />
** <u>Public announcement</u> of Release<br />
<br />
<br />
<br />
</td><br />
<td style="min-width:40%"><br />
=== Minor Release ===<br />
<br />
* Decide on new Release (Day: approx -4 / Duration 3-4 days)<br />
** Check internally with the team (3 to 4 days)<br />
** Release Date will be 2 weeks from announcement (1 week for community feedback)<br />
* Prepare<br />
** Update fixes page on wiki (New section "Fixes for ..." next release<br />
<br />
* Announce upcoming Release (Day: 0 / Duration 7-8 days)<br />
** <u>Special announcement</u> for translators.<br />
** <u>Public announcement</u> with approx date.<br />
*** Mail<br />
*** Forum<br />
: Ask for <br />
:*new regressions<br />
:*missing merges<br />
<br />
Community has about 1 week to respond.<br />
Urgent matters can still be reported during "approve" phase.<br />
<br />
Depending on feedback received: reschedule and if needed re-announce.<br />
<br />
==== Approve Release (Start at day: 7 to 9 / Duration 3/4 days) ====<br />
<br />
* Check internally with the team (3 to 4 days)<br />
<br />
Depending on feedback received: reschedule and if needed re-announce.<br />
<br />
==== Prepare Release (Start at day: 10 to 12 / Duration 3/4 days) ====<br />
<br />
* TAG SVN<br />
* Build docs => update binaries svn<br />
* Start building<br />
* Build Release binaries<br />
** Test<br />
* Upload (staged)<br />
<br />
At the same time:<br />
* Update mantis<br />
* Collect checksums and commit to web-page svn<br />
** Optional: Update webpage to contain checksums<br />
* Update web-page svn with new download links (to be published *after* release)<br />
<br />
==== Release (At day 14 to 16) ====<br />
<br />
* Make files public on Sourceforge (un-stage)<br />
** Set files as current release<br />
<br />
* <u>Public announcement</u> of Release<br />
** Mail<br />
** Forum<br />
** Twitter<br />
** Sourceforge news<br />
<br />
* Update web-page (Download-links / Checksums)<br />
<br />
<br />
</td><br />
</tr></table><br />
<br />
== Merging fixes ==<br />
<br />
Team members will add any revision that should be merged to the "fixes branch" page (see "Related Pages").<br />
<br />
User can contact Team members<br />
* on the forum<br />
* on the mail list<br />
* on open & assigned mantis reports<br />
to request revisions to be added.<br />
<br />
== Other Items ==<br />
<br />
In addition to the items on the checklist:<br />
* create new targets on Mantis, with each RC, and for the Release<br />
* Publish checksums on webpage<br />
* Update links on webpage<br />
* Updating of help files<br />
* Announcements for Translators<br />
<br />
TODO:<br />
Decide on channels for public feedback on:<br />
* Open regressions<br />
* Missing merges<br />
<br />
== Checklist (Suggestions) ==<br />
<br />
Please add suggestions here. <br />
The editing of the Checklist above should be reserved to the team. Thanks.<br />
<br />
== How to merge ==<br />
<br />
The lazarus developers have decided to use the native svn merge for this branch. Other branches used the svnmerge.py script to manage the revisions to be merged.<br />
<br />
* TODO: Maybe this information should be put in a separate page.<br />
<br />
[[SVN_Migration#Merge_with_plain_svn]]<br />
<br />
=== Using TortoiseSVN ===<br />
As noted in the link above using TortoiseSVN is more or less self explaining. <br />
<br />
* TODO: add some screen shots.<br />
<br />
When committing, in the recent messages a nice commit message is available.<br />
<br />
Tested with TortoiseSVN 1.7.6, SVN 1.7.4.<br />
<br />
=== Show list of revision that can be merged ===<br />
<br />
In the lazarus fixes_1_0 directory do:<br />
svn mergeinfo ^/trunk --show-revs eligible<br />
Or if you don't have a fixes directory checked out, you can pass the URL path of the fixes branch:<br />
svn mergeinfo ^/trunk http://svn.freepascal.org/svn/lazarus/branches/fixes_1_0 --show-revs eligible<br />
<br />
=== Show list of revision that have been merged ===<br />
<br />
In the lazarus fixes_1_0 directory do:<br />
svn mergeinfo ^/trunk --show-revs merged<br />
<br />
=== Merging revisions from trunk ===<br />
<br />
To merge one or more revisions from trunk, use the svn merge command. For example to merge revision 36506 and 36510 use:<br />
svn merge -c 36506,36510 ^/trunk<br />
<br />
To generate a commit log message, use:<br />
svn log ^/trunk -c 36506,36510 > svnmerge-commit-message.txt<br />
<br />
Edit svnmerge-commit-message.txt with your favorite text editor and add a first line like:<br />
Merged revision 36506,36510 from /trunk.<br />
<br />
Commit this with:<br />
svn commit -F svnmerge-commit-message.txt<br />
<br />
=== Blocking a revision to be merged ===<br />
<br />
Sometimes you want to block a revision to be merged, i.e. you want to make sure that this revision is never merged to the fixes branch, for example, because it contains a new feature or it contains a version number change not applicable to the fixes branch, but only to trunk. To block revision 36507, use:<br />
svn merge -c 36507 --record-only ^/trunk<br />
Then you can create a log message with:<br />
svn log ^/trunk -c 36507 > svnmerge-commit-message.txt<br />
Edit svnmerge-commit-message.txt with your favorite text editor and add first line like:<br />
Blocked revision 36507 from /trunk<br />
Then commit this change:<br />
svn commit -F svnmerge-commit-message.txt<br />
<br />
== Previous releases ==<br />
<br />
* [[Lazarus 0.9.30 todo]]<br />
* [[Lazarus 0.9.28 todo]]<br />
* [[Detailed Lazarus 0.9.22 todo|Lazarus 0.9.22 todo]]<br />
<br />
* [[Lazarus 0.9.30.2 release plan]]<br />
* [[Lazarus 0.9.28.2 release plan]]<br />
<br />
== See also ==<br />
<br />
* [[Detailed Lazarus release template todo]]<br />
<br />
== Related pages ==<br />
<br />
{{Navbar Lazarus Release Notes}}<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Development]]<br />
[[Category:Release engineering]]<br />
[[Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Lazarus_Continuous_Integration&diff=153489Lazarus Continuous Integration2022-08-16T15:36:26Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>The page describes GitLab continous integration setup for Lazarus project<br />
<br />
==Continuous Integration==<br />
On every commit CI system creates a pipeline, which builds Lazarus for<br />
several platforms/widgetsets. If the build fails, it notifies the<br />
creator of commit which caused the failure.<br />
<br />
The building process happens in a virtual machine (Docker container),<br />
which loads an operating system image. Our image is based on Debian 11<br />
x86_64 and currently contains GTK2, GTK3, Qt5 libraries and FPC<br />
(versions 3.2.0, 3.2.2) for x86_64-linux with x86_64-win64 and<br />
i386-win32 cross-compilers.<br />
<br />
Docker image configuration is defined in Dockerfile.buildenv file, and<br />
pipeline configuration is defined in .gitlab-ci.yml (both files located<br />
in Lazarus root directory).<br />
<br />
===Pipe Line===<br />
Our pipeline currently consists of three stages:<br />
<br />
1. Update our Docker image (see above). This process is triggered<br />
manually and should be only run when Dockerfile.buildenv and/or FPC<br />
version constants in .gitlab-ci.yml file were updated. Don't do this<br />
without consulting with me first.<br />
<br />
2. Update Qt5Pas bindings and cache the result of this job. This process<br />
is triggered automatically when `lcl/interfaces/qt5/cbindings` directory<br />
contents are changed. Its results are required for subsequent building<br />
of Lazarus with Qt5 widgetset. This is needed because building Qt5<br />
bindings takes a long time (~20 min) and they are rarely changed.<br />
<br />
3. Build Lazarus for various widgetsets/platforms. Jobs currently<br />
defined are building Lazarus for x86_64-gtk2, x86_64-qt5, x86_64-win64,<br />
i386-win32 with two FPC versions (currently FPC 3.2.0 [oldstable] and<br />
3.2.2 [stable]). x86_64-gtk2 (FPC 3.2.2), x86_64-qt5 (FPC 3.2.2) and<br />
i386-win32 (FPC 3.2.0) are built automatically on every commit. All<br />
others can be triggered manually via Gitlab web interface.<br />
<br />
In [https://gitlab.com/freepascal.org/lazarus/lazarus Gitlab web interface] you can notice `CI/CD` menu item at the left. By<br />
clicking `CI/CD`->`Pipelines` you will get to pipelines list, where you<br />
can see build status for each commit. By clicking circles in the<br />
`Stages` column you will get to submenus which will show status of all<br />
jobs and will allow to run/rerun them manually (by clicking at<br />
`play`/`retry` button) and to look at job's log (by clicking at job's name).<br />
<br />
Normally you will see only two stages (1 and 3). Stage 2 is only shown<br />
when Gitlab thinks that Qt5Pas bindings have to be rebuilt<br />
(`lcl/interfaces/qt5/cbindings` directory contents are changed).<br />
<br />
'''Important notice for Qt widgetsets:'''<br />
<br />
They use cached results from stage 2. In absolute majority of the builds<br />
this should not cause any problems. But still if you get Qt widgetset<br />
build failure related to Qt5Pas version mismatch or missing cache, make<br />
sure to go to the `Pipelines` page, stop the currently running pipeline<br />
if needed, then press `Clear runner caches` button, then press `Run<br />
pipeline` button (you will be asked about defining variables, just skip<br />
this step (we don't use them) and confirm running pipeline).<br />
<br />
==See Also==<br />
[[Category:CI/CD]]<br />
[[Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Developer_pages&diff=153488Developer pages2022-08-16T15:36:13Z<p>Skalogryz: /* FPC Developer Team */</p>
<hr />
<div>Animations of commit history made with gource by Bernd K. :<br />
<br />
11 years of Lazarus development: http://vimeo.com/37066125<br />
<br />
Eight months in 2011-2012: http://vimeo.com/36868024<br />
<br />
== Lazarus Developer Team ==<br />
<br />
{| class="wikitable"<br />
! Member !! Photo !! Role in Lazarus !! IRC nick !! GIT nick !! GMT<br />
|----<br />
|[[user:AndrewH|Andrew Haines]]||(no photo)||Gtk2, LHelp||drewski||?||GMT-05<br />
|---<br />
|[[user:Ask|Alexander Klenin]]||(no photo)||TAchart||_Ask_||?||GMT+10<br />
|----<br />
|Balázs Székely||(no photo)||||||balazs_szekely||?<br />
|----<br />
|[[user:Bart|Bart Broersma]]||(no photo)||LCL, Win32||||flyingsheep||GMT+01<br />
|----<br />
|[[user:Kirkpatc|Chris Kirkpatrick]]||(no photo)||not active currently, was documentation||?||?||GMT+00<br />
|----<br />
|[[user:Dblaszijk|Darius Blaszijk]]||(no photo)||not active currently, was IDE||daar||?||GMT+01<br />
|----<br />
|[[User:Skalogryz|Dmitry Boyarintsev]]||(no photo)||cocoa, carbon, LCL||skalogryz||?||?<br />
|----<br />
|Don Siders||(no photo)||Documentation||?||dsiders||?<br />
|----<br />
|[[user:Sekelsenmat|Felipe Monteiro de Carvalho]]||(no photo)||LCL, customdrawn widgetset||sekelsenmat||?||GMT+01<br />
|----<br />
|[[user:Jesusrmx|Jesus Reyes]]||(no photo)||database controls||jesusra||jramx||GMT-06<br />
|----<br />
|[[user:JuhaManninen|Juha Manninen]]||[[Image:JuhaManninen.jpg|100px]]||IDE, Delphi Converter||JuhaManninen||JuhaManninen||GMT+02<br />
|----<br />
|[[user:Wile64|Laurent Jacques]]||(no photo)||not active currently, was images||?||?||GMT+01<br />
|----<br />
|[[User:Luizmed|Luiz Américo Pereira Câmara]]||[[Image:luiz_americo.png|100px]]||LCL,win32,gtk2||blikblum||?||GMT-03<br />
|----<br />
|[[user:Paul_Ishenin|Paul Ishenin]]||[[Image:paul_ishenin.jpg|100px]]||LCL, win32, IDE||Paul_Ishenin||?||GMT+07<br />
|----<br />
|[[user:Marc|Marc Weustink]]||(no photo)||LCL, debugger||giantm||mweustink||GMT+01<br />
|----<br />
|[[user:Martin|Martin Friebe]]||[[Image:MartinFriebe.jpg|100px]]||synedit + IDE source editor, debugger||martin_frb||martin_frb||GMT+00<br />
|----<br />
|[[user:Mattias2|Mattias Gärtner]]||(no photo)||codetools, LCL, IDE||lazidealist||lazidealist||GMT+01<br />
|----<br />
|[[user:Maxim|Maxim Ganetsky]]||(no photo)||i18n, l10n, IDE, LCL||KritiK||ganmax||GMT+04<br />
|----<br />
|[[user:Mazen|Mazen NEIFER]]||(no photo)||linux packages||?||mazen.neifer||??<br />
|----<br />
|[[user:Michael|Michael Hess]]||(no photo)||not active currently, lazarus founder||?||?||??<br />
|----<br />
|[[user:Michl|Michael W. Vogel]]||[[Image:MichaelVogel.png|100px]]||LCL, Win32||?||michl1||GMT+01<br />
|----<br />
|[[user:Neli|Micha Nelissen]]||(no photo)||not active currently, was win32||neli||?||GMT+01<br />
|----<br />
|[[user:Ondrej|Ondrej Pokorny]]||(no photo)||not active currently||?||onpok||GMT+01<br />
|----<br />
|[[user:Tombo|Tom Gregorovic]]||(no photo)||not active currently, was carbon||Tombo||?||GMT+01<br />
|----<br />
|[[user:Vincent|Vincent Snijders]]||[[Image:Vincent_Snijders.jpg|100px]]||wiki server maintainer, previously active for win32, as release manager, fixes branch supporter||fpcfan||?||GMT+01<br />
|----<br />
|[[user:wp|Werner Pamler]]||(no photo)|| TAChart, various components || ? ||wpam|| GMT+01<br />
|----<br />
|[[user:Zeljan|Zeljan Rikalo]]||[[Image:Zeljan.jpg|100px]]||qt, gtk2, LCL||zeljan||zeljan1||GMT+01<br />
|}<br />
<br />
* See also [https://gitlab.com/freepascal.org/lazarus/lazarus/-/project_members Project Members on GitLab]<br />
<br />
== FPC Developer Team ==<br />
{{stub|reason=[[#FPC Developer Team|empty section]] [[User:Kai Burghardt|Kai Burghardt]] ([[User talk:Kai Burghardt|talk]]) 17:38, 30 April 2020 (CEST)}}<br />
<br />
* see also [https://freepascal.org/aboutus.html Free Pascal development team]<br />
* see also [https://gitlab.com/groups/freepascal.org/fpc/-/group_members FPC group members on GitLab]<br />
<br />
[[Category:Lazarus internals]]<br />
[[Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:Lazarus_Developer_Guide&diff=153487Category:Lazarus Developer Guide2022-08-16T15:35:48Z<p>Skalogryz: Skalogryz moved page Category:Lazarus Developer Guide to Category:Lazarus Developer Routine: the word guide is used too much often. The word "routine" imposes more boring aspect of the human life and thus stands out</p>
<hr />
<div>#REDIRECT [[:Category:Lazarus Developer Routine]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Qemu_and_other_emulators&diff=153485Qemu and other emulators2022-08-16T15:30:44Z<p>Skalogryz: /* See also */</p>
<hr />
<div>This note covers setting up Qemu on an x86-based development system running Linux. This allows native (rather than cross) development tools to be run, which can be useful:<br />
* where the target system has performance/resource issues (e.g. some ARM systems)<br />
* is not run natively due to company policy (older versions of Microsoft Windows)<br />
* is quite simply unavailable at a reasonable price (e.g. SGI MIPS systems or the Chinese MIPS-based systems).<br />
<br />
It also briefly mentions User Mode Linux for x86 and the Hercules emulator for IBM zSeries mainframes despite the fact that these are not particularly relevant to Free Pascal, and Docker containerisation. It does not consider x86-on-x86 virtualisation systems such as VMware, Microsoft Hyper-V or Oracle VirtualBox, and only considers Linux KVM as a foundation technology for Qemu.<br />
<br />
== The Host System ==<br />
<br />
In the current case, the host is a Compaq rack-mount server running at around 3GHz with several Gb RAM; be warned that performance will drop off drastically with a lower specification system. It has two internal drive cages, the first is connected to a RAID controller and is used for the host operating system and tools, the second is connected to a SCSI controller and contains 6x discs each of which is used for a different guest system.<br />
<br />
The host IP address is 192.168.1.22 and the system is named pye-dev-07, the default gateway and name server are on 192.168.1.1. Guest systems are on the 192.168.22.x subnet and are named pye-dev-07a (192.168.22.16), pye-dev-07b (192.168.22.17) and so on, they have their own gateway 192.168.22.1 which is known to the site router and firewalls.<br />
<br />
The host operating system is Debian "Squeeze", the host normally runs headless and may be accessed by SSH, X using XDMCP, or VNC. The display manager is gdm since this has a better XDMCP implementation than the alternatives, however in practice graphical login is most often handled by VNC.<br />
<br />
The following guests are implemented:<br />
<br />
; pye-dev-07a : Debian on ARM (little-endian, armel) using Qemu<br />
<br />
; pye-dev-07b : Debian on MIPS (little-endian, mipsel) using Qemu<br />
<br />
; pye-dev-07c : Slackware x86 13.37 using Qemu<br />
<br />
; pye-dev-07d : Slackware x86 13.37 using User Mode Linux<br />
<br />
; pye-dev-07e : Windows 2K using Qemu<br />
<br />
; pye-dev-07f : Debian on zSeries using the Hercules emulator<br />
<br />
; pye-dev-07g : Debian on 68k using the Aranym emulator<br />
<br />
Originally, pye-dev-07a was earmarked for big-endian ARM, but this appears to be being phased out by Debian so is probably no longer a viable target. Anybody planning to port FPC to the AVR-based Arduino?<br />
<br />
In general, multiple guests can run simultaneously although this has not been exhaustively tested recently.<br />
<br />
In the case of Linux the guest systems are each installed on an 18Gb disc, in the case of Windows a 36Gb disc is used. Each disc is assigned a label using e2label (arm, armel and so on), so that the startup script can mount it by name irrespective of which drive cage slot it's in.<br />
<br />
== Debian Guest using Qemu ==<br />
<br />
Select a suitable Debian mirror and version, for example<br />
<br />
http://ftp.de.debian.org/debian/dists/squeeze/main/...<br />
<br />
Fetch a kernel and initrd image for Debian Squeeze, as below.<br />
<br />
<u>For ARM (little-endian):</u><br />
.../main/installer-armel/current/images/versatile/netboot/vmlinux-2.6.32-5-versatile<br />
.../main/installer-armel/current/images/versatile/netboot/initrd.gz<br />
<br />
In addition for this architecture you also need:<br />
<br />
http://people.debian.org/~aurel32/qemu/armel/initrd.img-2.6.32-5-versatile<br />
<br />
<u>For MIPS (little-endian):</u><br />
.../main/installer-mipsel/current/images/malta/netboot/vmlinux-2.6.32-5-4kc-malta<br />
.../main/installer-mipsel/current/images/malta/netboot/initrd.gz<br />
<br />
Copy these to the disc reserved for the guest, e.g. /export/mipsel.<br />
<br />
Create a filesystem for Qemu, e.g.:<br />
<br />
<syntaxhighlight lang="bash"># qemu-img create -f qcow mipsel_hda.img 16G</syntaxhighlight><br />
<br />
Expect that to round up to around 17.1 Gb, if it doesn't then experiment. Start Qemu, telling it what kernel, initrd and filesystem to use:<br />
<br />
<u>For ARM (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-arm -M versatilepb -kernel vmlinuz-2.6.32-5-versatile -initrd initrd.gz \<br />
-hda armel_hda.img -append root=/dev/ram<br />
</syntaxhighlight><br />
<br />
Note that the above command requires X access, e.g. ssh with the -X option.<br />
<br />
<u>For MIPS (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta -initrd initrd.gz \<br />
-hda mipsel_hda.img -append "root=/dev/ram console=ttyS0" -nographic<br />
</syntaxhighlight><br />
<br />
Install the guest operating system as usual, splitting the disc into 16.5Gb for / with the remainder (around 700Mb) as swap. This will be slow, 8 or 9 hours is not implausible, so make sure that nobody's about to turn off your mains or disconnect you from the Internet.<br />
<br />
Don't worry if it tells you it's not installing a loader- it's not needed since the kernel and initrd are loaded into memory by the host.<br />
<br />
Boot the operating system and set network addresses etc. Use 192.168.22.16 or similar, with a gateway of 192.168.22.1.<br />
<br />
<u>For ARM (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-arm -M versatilepb -kernel vmlinuz-2.6.32-5-versatile \<br />
-initrd initrd.img-2.6.32-5-versatile -hda armel_hda.img -append "root=/dev/sda1"<br />
</syntaxhighlight><br />
<br />
<u>For MIPS (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta \<br />
-hda mipsel_hda.img -append "root=/dev/sda1 console=ttyS0" -nographic<br />
</syntaxhighlight><br />
<br />
Finally, you should be able to boot the operating system with an operational network. This relies on having /etc/qemu-ifup and /etc/qemu-ifdown files (see below), and passes additional parameters to them in shell variables. In outline:<br />
<br />
<u>For ARM (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-arm -M versatilepb -m 256 -hda armel_hda.img \<br />
-kernel vmlinuz-2.6.32-5-versatile -initrd initrd.img-2.6.32-5-versatile \<br />
-append 'root=/dev/sda1 text' \<br />
-net nic,macaddr=00:16:3e:00:00:01 -net tap,ifname=tun1<br />
</syntaxhighlight><br />
<br />
<u>For MIPS (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
# qemu-system-mipsel -M malta -m 256 -hda mipsel_hda.img \<br />
-kernel vmlinux-2.6.32-5-4kc-malta -no-reboot \<br />
-append 'root=/dev/sda1 console=ttyS0' -nographic \<br />
-net nic,macaddr=00:16:3e:00:00:02 -net tap,ifname=tun2<br />
</syntaxhighlight><br />
<br />
Remember that if you change the network interface type or MAC address you will probably need to delete entries from the guest's /etc/udev/rules.d/z??_persistent-net.rules file.<br />
<br />
== Windows 2K Guest using Qemu ==<br />
<br />
Use dd to save a .iso image of the installation CD. Create a filesystem image:<br />
<br />
<syntaxhighlight lang="bash"># qemu-img create -f qcow2 win2k.img 32G</syntaxhighlight><br />
<br />
Boot using startup script as below. Note that this must specify a non-default network card, since Qemu's current (as of 2011) default is not supported by Windows 2K.<br />
<br />
''TODO: run with kernel support module.''<br />
<br />
== KVM on a Linux host ==<br />
<br />
KVM (Kernel-based Virtual Machine) is an enabling API supported on more recent x86 and x86-64 (AMD64) systems, this replaces the older KQemu kernel module which is now deprecated by both Qemu and the kernel.<br />
<br />
KVM is typically enabled by the host system BIOS. If not enabled by the BIOS it cannot be enabled by the kernel or by (a suitably privileged) application program, since the x86 architecture requires power to be cycled to change the state of this facility. The result of this is that KVM might be unavailable (no /dev/kvm device) even if it shown as supported by the CPU flags in /proc/cpuinfo.<br />
<br />
== Common Qemu startup, ifup and ifdown scripts ==<br />
<br />
There is much commonality irrespective of whether the guest is running Linux or Windows.<br />
<br />
<u>First startup script (e.g. /export/C):</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
mount -L mipsel<br />
cd /export/mipsel<br />
. ./C-2<br />
</syntaxhighlight><br />
<br />
<u>Second startup script for ARM (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
# Routine startup of a Qemu guest relies on (the host) running /etc/qemu-ifup<br />
# to condition ARP, forwarding etc.<br />
<br />
QEMU_ID=1<br />
QEMU='qemu-system-arm -M versatilepb'<br />
QEMU_RAM='-m 256'<br />
QEMU_HD='-hda armel_hda.img'<br />
QEMU_CD=''<br />
QEMU_BOOT="-kernel vmlinuz-2.6.32-5-versatile -append 'root=/dev/sda1 text' -initrd initrd.img-2.6.32-5-versatile"<br />
# QEMU_MONITOR='-monitor stdio -nographic'<br />
# QEMU_MONITOR='-nographic'<br />
QEMU_VGA=''<br />
VNC_ID=$(($QEMU_ID+1))<br />
# QEMU_VNC="-vnc :$VNC_ID -k en-gb"<br />
QEMU_VNC=''<br />
QEMU_NET="-net nic,macaddr=00:16:3e:00:00:0$QEMU_ID -net tap,ifname=tun$QEMU_ID"<br />
QEMU_GUEST_IP_ADDRESS=192.168.22.17<br />
QEMU_GUEST_IP_GATEWAY=192.168.22.1<br />
QEMU_HOST_GATEWAY_IF=eth1<br />
export QEMU_GUEST_IP_ADDRESS QEMU_GUEST_IP_GATEWAY QEMU_HOST_GATEWAY_IF<br />
<br />
echo \* $QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC<br />
<br />
screen -S QEMU_$QEMU_ID \<br />
sh -c "$QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC"<br />
<br />
cd ..<br />
</syntaxhighlight><br />
<br />
<u>Second startup script for MIPS (little-endian):</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
# Routine startup of a Qemu guest relies on (the host) running /etc/qemu-ifup<br />
# to condition ARP, forwarding etc.<br />
<br />
QEMU_ID=2<br />
QEMU='qemu-system-mipsel -M malta'<br />
QEMU_RAM='-m 256'<br />
QEMU_HD='-hda mipsel_hda.img'<br />
QEMU_CD=''<br />
QEMU_BOOT="-kernel vmlinux-2.6.32-5-4kc-malta -append 'root=/dev/sda1 console=ttyS0' -no-reboot"<br />
# QEMU_MONITOR='-monitor stdio -nographic'<br />
QEMU_MONITOR='-nographic'<br />
QEMU_VGA=''<br />
VNC_ID=$(($QEMU_ID+1))<br />
# QEMU_VNC="-vnc :$VNC_ID -k en-gb"<br />
QEMU_VNC=''<br />
QEMU_NET="-net nic,macaddr=00:16:3e:00:00:0$QEMU_ID -net tap,ifname=tun$QEMU_ID"<br />
QEMU_GUEST_IP_ADDRESS=192.168.22.18<br />
QEMU_GUEST_IP_GATEWAY=192.168.22.1<br />
QEMU_HOST_GATEWAY_IF=eth1<br />
export QEMU_GUEST_IP_ADDRESS QEMU_GUEST_IP_GATEWAY QEMU_HOST_GATEWAY_IF<br />
<br />
echo \* $QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC<br />
<br />
screen -S QEMU_$QEMU_ID \<br />
sh -c "$QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC"<br />
<br />
cd ..<br />
</syntaxhighlight><br />
<br />
<u>Second startup script for Windows:</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
# Routine startup of a Qemu guest relies on (the host) running /etc/qemu-ifup<br />
# to condition ARP, forwarding etc.<br />
<br />
QEMU_ID=4<br />
QEMU=qemu<br />
QEMU_RAM='-m 256'<br />
QEMU_HD='-hda win2k.img'<br />
QEMU_CD='-cdrom Windows2k-SP4.iso'<br />
QEMU_BOOT='-boot c'<br />
QEMU_MONITOR='-monitor stdio'<br />
QEMU_VGA='-vga cirrus'<br />
VNC_ID=$(($QEMU_ID+1))<br />
QEMU_VNC="-vnc :$VNC_ID -k en-gb"<br />
QEMU_NET="-net nic,macaddr=00:16:3e:00:00:0$QEMU_ID,model=rtl8139 -net tap,ifname=tun$QEMU_ID"<br />
QEMU_GUEST_IP_ADDRESS=192.168.22.20<br />
QEMU_GUEST_IP_GATEWAY=192.168.22.1<br />
QEMU_HOST_GATEWAY_IF=eth1<br />
export QEMU_GUEST_IP_ADDRESS QEMU_GUEST_IP_GATEWAY QEMU_HOST_GATEWAY_IF<br />
<br />
echo \* $QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC<br />
<br />
screen -S QEMU_$QEMU_ID \<br />
$QEMU $QEMU_RAM $QEMU_HD $QEMU_CD $QEMU_BOOT \<br />
$QEMU_MONITOR $QEMU_VGA $QEMU_NET $QEMU_VNC<br />
<br />
cd ..<br />
</syntaxhighlight><br />
<br />
<u>/etc/qemu-ifup (for both Linux and Windows):</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
<br />
# if-up file for qemu, heavily cribbed from the command sequence embedded in<br />
# User Mode Linux. MarkMLl.<br />
<br />
echo Running /etc/qemu-ifup $1 $2...<br />
<br />
# For compatibility with UML the only parameter here is $1 which is the<br />
# interface name. I've put in a reference to $2 so we can see it if anything<br />
# changes.<br />
<br />
# I'm going to assume that qemu is always run by root. This is fairly<br />
# reasonable since it allows guest OSes to be fired up which themselves might<br />
# give access to confidential data etc. if compromised.<br />
<br />
# Here's my equivalent to the host-side UML setup for Qemu. We're hamstrung<br />
# here by the fact that the emulator is not telling us what IP address it's<br />
# trying to enable, there isn't a 1:1 correspondence between IP addresses and<br />
# interfaces since the latter depends on the order the sessions are started.<br />
#<br />
# As a hack, assume that the caller exports QEMU_GUEST_IP_ADDRESS (e.g.<br />
# 192.168.17.16), QEMU_GUEST_IP_GATEWAY (e.g. 192.168.17.1) and<br />
# QEMU_HOST_GATEWAY_IF (e.g. eth0).<br />
<br />
echo \* modprobe tun<br />
modprobe tun<br />
<br />
echo \* ifconfig $1 $QEMU_GUEST_IP_GATEWAY netmask 255.255.255.255 up<br />
ifconfig $1 $QEMU_GUEST_IP_GATEWAY netmask 255.255.255.255 up<br />
<br />
X=`cat /proc/sys/net/ipv4/ip_forward`<br />
if [ "$X" == "0" ]; then<br />
<br />
# Use either this...<br />
<br />
# echo Global forwarding is not enabled. Please refer to the administrator<br />
# echo responsible for this machine, enabling it might be a security hazard.<br />
<br />
# ...or this.<br />
<br />
echo Forcibly enabling global forwarding, note that this might be a security hazard.<br />
echo \* echo 1 \> /proc/sys/net/ipv4/ip_forward<br />
echo 1 > /proc/sys/net/ipv4/ip_forward<br />
X=`cat /proc/sys/net/ipv4/ip_forward`<br />
if [ "$X" == "0" ]; then<br />
echo Unable to enable global forwarding. Please refer to the administrator<br />
echo responsible for this machine.<br />
fi<br />
fi<br />
<br />
echo \* route add -host $QEMU_GUEST_IP_ADDRESS dev $1<br />
route add -host $QEMU_GUEST_IP_ADDRESS dev $1<br />
<br />
echo \* echo 1 \> /proc/sys/net/ipv4/conf/$1/proxy_arp<br />
echo 1 > /proc/sys/net/ipv4/conf/$1/proxy_arp<br />
X=`cat /proc/sys/net/ipv4/conf/$1/proxy_arp`<br />
if [ "$X" == "0" ]; then<br />
echo -n Retrying<br />
while [ "$X" == "0" ]; do<br />
sleep 1<br />
echo -n .<br />
echo 1 > /proc/sys/net/ipv4/conf/$1/proxy_arp<br />
X=`cat /proc/sys/net/ipv4/conf/$1/proxy_arp`<br />
done<br />
echo OK<br />
fi<br />
<br />
echo \* arp -Ds $QEMU_GUEST_IP_ADDRESS $1 pub<br />
arp -Ds $QEMU_GUEST_IP_ADDRESS $1 pub<br />
<br />
echo \* arp -Ds $QEMU_GUEST_IP_ADDRESS $QEMU_HOST_GATEWAY_IF pub<br />
arp -Ds $QEMU_GUEST_IP_ADDRESS $QEMU_HOST_GATEWAY_IF pub<br />
<br />
# Set up experimental UDP proxies. Depending on the protocol of interest<br />
# messages in one or both directions might need to be relayed.<br />
#<br />
# UDP port 79 is used for Dialarm signals, a unidirectional proxy is<br />
# adequate for this but detection of hosts changing state (i.e. being<br />
# added to or removed from the population of cooperating systems) is far<br />
# more responsive if a bidirectional proxy is available.<br />
<br />
PROXY_ID=1<br />
case "$1" in<br />
tun1)<br />
PROXY_ID=2<br />
;;<br />
tun2)<br />
PROXY_ID=3<br />
;;<br />
tun3)<br />
PROXY_ID=4<br />
;;<br />
tun4)<br />
PROXY_ID=5<br />
;;<br />
tun5)<br />
PROXY_ID=6<br />
;;<br />
tun6)<br />
PROXY_ID=7<br />
;;<br />
tun7)<br />
PROXY_ID=8<br />
;;<br />
esac<br />
<br />
# echo \* udp-broadcast-relay -f $PROXY_ID 79 $QEMU_HOST_GATEWAY_IF $1<br />
# /usr/local/src/udp-broadcast-relay/udp-broadcast-relay-0.3/udp-broadcast-relay \<br />
# -f $PROXY_ID 79 $QEMU_HOST_GATEWAY_IF $1<br />
<br />
# Alternatively use this one which is oriented towards IP addresses<br />
# rather than interfaces.<br />
<br />
# Note attempt to counteract any niceness applied to Qemu itself.<br />
<br />
ps ax | grep 'udp-proxy[ ]-z 79 ' >/dev/null 2>&1<br />
if [ $? != 0 ]; then<br />
echo \* udp-proxy -z 79 $QEMU_GUEST_IP_ADDRESS<br />
/usr/bin/nice --adjustment=20 /usr/local/src/udp-proxy/udp-proxy -z 79 $QEMU_GUEST_IP_ADDRESS<br />
else<br />
echo \* Already running udp-proxy -z 79 $QEMU_GUEST_IP_ADDRESS<br />
fi<br />
<br />
# echo \* udp-proxy -z 13264 $QEMU_GUEST_IP_ADDRESS<br />
# /usr/local/src/udp-proxy/udp-proxy -z 13264 $QEMU_GUEST_IP_ADDRESS<br />
<br />
echo .../qemu/qemu-ifup completed.<br />
</syntaxhighlight><br />
<br />
<u>/etc/qemu-ifdown (for both Linux and Windows):</u><br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
echo \* route del -host $QEMU_GUEST_IP_ADDRESS dev $1<br />
route del -host $QEMU_GUEST_IP_ADDRESS dev $1<br />
<br />
echo \* ifconfig $1 down<br />
ifconfig $1 down<br />
</syntaxhighlight><br />
<br />
In actual fact, these operations were cribbed from User Mode Linux (below) where they are embedded inside a host library.<br />
<br />
== Slackware x86 Guest using User Mode Linux ==<br />
<br />
User Mode Linux runs a guest kernel as a standard program, i.e. there is no emulation or virtualisation involved. The guest kernel can be allocated either physical discs or filesystems contained in files.<br />
<br />
Put a .iso corresponding to a recent Slackware DVD in /export/uml. Unpack the initrd using zcat and cpio, save it as an ext3 image initrd_unpacked. Using dd create an empty file root_fs_slackware which will be partitioned and formatted during installation.<br />
<br />
Use the sources from e.g. a recent Slackware to compile kernel plus modules with ARCH=um using a suffix -uml. Save the kernel to /export/uml/linux, install the modules and then copy them into the initrd filesystem.<br />
<br />
Boot the UML kernel, telling it to use the initrd image and DVD iso:<br />
<br />
<syntaxhighlight lang="bash"># ./linux ubd0=initrd_unpacked ubd1=root_fs_slackware fake_ide ubd2r=slackware-13.37-install-dvd.iso rw</syntaxhighlight><br />
<br />
Run fdisk and setup as normal, you might need to tell it to install to /dev/ubd1 and use /dev/ubd2 for source. Finally, copy the modules onto the target filesystem.<br />
<br />
When complete start up like this:<br />
<br />
<syntaxhighlight lang="bash"><br />
# Routine startup of a UML guest relies on (the host) running /usr/lib/uml/uml_net<br />
# to condition ARP, forwarding etc.<br />
<br />
echo \* ./linux ubd0=initrd_unpacked ubd1=root_fs_slackware fake_ide ubd2r=slackware-13.37-install-dvd.iso \<br />
root=/dev/ubdb1 eth0=tuntap,,,192.168.1.22<br />
<br />
screen -S UML_3 \<br />
./linux ubd0=initrd_unpacked ubd1=root_fs_slackware fake_ide ubd2r=slackware-13.37-install-dvd.iso \<br />
root=/dev/ubdb1 eth0=tuntap,,,192.168.1.22<br />
<br />
cd ..<br />
</syntaxhighlight><br />
<br />
Note that this is usually run from an X session, since the multiple virtual consoles appear as separate xterms. Also see [http://wiki.freepascal.org/linux/kernel/user_mode_VM].<br />
<br />
== Debian 68k Guest using Aranym ==<br />
<br />
<syntaxhighlight lang="bash"># apt-get install aranym</syntaxhighlight><br />
<br />
I (MarkMLl) note this installs bridge-utils but instead am going to use my standard Qemu-style startup scripts, which themselves were originally based on sequences executed internally by UML; note that Hercules for zSeries (above) is the odd-one-out here since the guest uses SLIP for networking.<br />
<br />
Referring to https://wiki.debian.org/Aranym/Quick, download https://www.freewrt.org/~tg/dc11m68k.tgz and check its signature. Unpack, noting a kernel vmlinuz-2.6.39-2-atari, a filesystem image dc11.img.xz and a configuration file aranym.config.<br />
<br />
In aranym.config, change the ETH0 section to read:<br />
<br />
Type = ptp<br />
HostIP = 192.168.22.1<br />
AtariIP = 192.168.22.22<br />
Netmask = 255.255.255.0<br />
<br />
Change the startup script runaranym to read:<br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
QEMU_GUEST_IP_ADDRESS=192.168.22.22<br />
QEMU_GUEST_IP_GATEWAY=192.168.22.1<br />
QEMU_HOST_GATEWAY=eth0<br />
export QEMU_GUEST_IP_ADDRESS QEMU_GUEST_IP_GATEWAY QEMU_HOST_GATEWAY<br />
<br />
/etc/qemu-ifup tap7<br />
<br />
cd "$(dirname "$0")"<br />
SDL_AUDIODRIVER=dummy; export SDL_AUDIODRIVER<br />
aranym-mmu -l -c aranym.config<br />
<br />
/etc/qemu-ifdown tap7<br />
</syntaxhighlight><br />
<br />
Uncompress the image file:<br />
<br />
<syntaxhighlight lang="bash"># unxz dc11.img.xz</syntaxhighlight><br />
<br />
Using xterm on a graphical login, run the startup script:<br />
<br />
<syntaxhighlight lang="bash"># runaranym</syntaxhighlight><br />
<br />
The result of that should be a console for the guest Debian system.<br />
<br />
On the guest console, login as root with password root, and immediately change the password to something appropriate using the passwd command. Change the hostname in /etc/hostname and /etc/hosts, the IP address etc. in /etc/network/interfaces, and the nameserver in /etc/resolv.conf. Reboot and check that the network is operational by pinging from the guest to the site router (e.g. 192.168.1.1) and then pinging the guest (192.168.22.22) from any convenient system; if this doesn't work fix it before continuing.<br />
<br />
Then as described on https://wiki.debian.org/Aranym/Quick run the three commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
# dpkg-reconfigure openssh-server<br />
# apt-get update<br />
# apt-get install popularity-contest<br />
</syntaxhighlight><br />
<br />
Finally edit /root/profile to remove the reminder to run the above. It should now be possible to login using SSH, and to continue to configure and use the system like any Debian guest.<br />
<br />
== Debian zSeries Guest using Hercules, without VM ==<br />
<br />
Hercules is a commercial-grade emulator for IBM mainframes, it is available as a standard package for e.g. Debian and related Linux distributions. Once the emulator is running, enter<br />
<br />
ipl 120<br />
<br />
to boot Linux from device 120. Hopefully SSH will be operational so you won't need to interact with the console, but if you do then prefix each line that is to go to the guest operating system (i.e. rather than to the console itself) with a dot.<br />
<br />
Refer to the URL in the script below for more details.<br />
<br />
Startup:<br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/sh<br />
<br />
# PREREQUISITE: Boot with ipl 120<br />
<br />
# Note that this makes no attempt to support IPv6.<br />
<br />
iptables -t nat -A POSTROUTING -o eth1 -s 192.168.22.0/24 -j MASQUERADE<br />
iptables -A FORWARD -s 192.168.22.0/24 -j ACCEPT<br />
iptables -A FORWARD -d 192.168.22.0/24 -j ACCEPT<br />
echo 1 > /proc/sys/net/ipv4/ip_forward<br />
echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp<br />
<br />
# http://www.josefsipek.net/docs/s390-linux/hercules-s390.html<br />
<br />
screen -S HERC_5 \<br />
hercules<br />
<br />
cd ..<br />
</syntaxhighlight><br />
<br />
Configuration:<br />
<br />
CPUSERIAL 000069 # CPU serial number<br />
CPUMODEL 9672 # CPU model number<br />
MAINSIZE 256 # Main storage size in megabytes<br />
XPNDSIZE 0 # Expanded storage size in megabytes<br />
CNSLPORT 3270 # TCP port number to which consoles connect<br />
NUMCPU 2 # Number of CPUs<br />
LOADPARM 0120.... # IPL parameter<br />
OSTAILOR LINUX # OS tailoring<br />
PANRATE SLOW # Panel refresh rate (SLOW, FAST)<br />
ARCHMODE ESAME # Architecture mode ESA/390 or ESAME<br />
<br />
# .-----------------------Device number<br />
# | .-----------------Device type<br />
# | | .---------File name and parameters<br />
# | | |<br />
# V V V<br />
#--- ---- --------------------<br />
<br />
# console<br />
001F 3270<br />
<br />
# terminal<br />
0009 3215<br />
<br />
# reader<br />
000C 3505 /export/zlinux/rdr/kernel.debian /export/zlinux/rdr/parmfile.debian /export/zlinux/rdr/initrd.debian autopad eof<br />
<br />
# printer<br />
000E 1403 /export/zlinux/prt/print00e.txt crlf<br />
<br />
# dasd<br />
0120 3390 /export/zlinux/dasd/3390.LINUX.0120<br />
0121 3390 /export/zlinux/dasd/3390.LINUX.0121<br />
<br />
# tape<br />
0581 3420<br />
<br />
# network s390 realbox<br />
# 0A00,0A01 CTCI -n /dev/net/tun -t 1500 10.1.1.2 10.1.1.1<br />
0A00,0A01 CTCI -n /dev/net/tun -t 1500 192.168.22.21 192.168.1.22<br />
<br />
Note that the guest network is configured as SLIP via a simulated CTC (Channel To Channel) device. Best not fooled with.<br />
<br />
== Debian zSeries Guest using Hercules, with VM ==<br />
<br />
This combination is unlikely to work using freely-available software, since Linux requires at least an S/390 G3 system while the most recent IBM VM available is VM/370. It might be technically possible to run a non-free VM on Hercules, but at the time of writing it is at best unclear whether this could be done legally.<br />
<br />
This means that it is not, for example, possible to run a VM host with simultaneous Linux and MUSIC/SP guests.<br />
<br />
== MUSIC/SP using Sim/390 or Hercules ==<br />
<br />
''This section is an outline only. Note that in the IBM ecosystem OS and VM are the names of specific operating systems, rather than being a generic abbreviation; in addition DOS is a '60s-era IBM operating system with no connection to Microsoft or PCs.''<br />
<br />
MUSIC/SP is a freely-available (but not open source) operating system which implements a subset of IBM's MVS API, i.e. is to some extent compatible with operating systems of the OS/360 lineage in particular OS/VS1, extended with some novel features including a filesystem with user-accessible directories. It does not provide an API compatible with Linux, and uses the EBCDIC character set.<br />
<br />
Unlike other freely-available OS-compatible operating systems (see for example [http://www.ibiblio.org/jmaynard/], [http://gunkies.org/wiki/Installing_VM/370_on_Hercules], and the section below), MUSIC/SP provides TCP/IP networking. However this requires that the IUCV (Inter User Communication Vehicle) be provided by the underlying platform and that there is a suitable network support kernel for it to talk to: these are normally made available by IBM's VM operating system (VM/SP or VM/ESA, a sufficiently-recent version of which is not freely available).<br />
<br />
Considering emulated environments, IUCV can be provided either by running a recent VM on top of Hercules, or by running the freely-available (but not open source) Sim/390 emulator on Windows. Hercules does not provide IUCV or a network support kernel directly (although as of early 2012 this might be being addressed), so while MUSIC/SP will run on Hercules it will not have TCP/IP-based networking facilities: use Sim/390 if you really need this.<br />
<br />
Regrettably, the maintainer of MUSIC/SP and Sim/390 is no longer active, and while binaries and documentation remain available for download via [http://www.canpub.com/teammpg/de/mcgweb/] the sources are not.<br />
<br />
== VM/370 using Hercules ==<br />
<br />
''This section is an outline only. Note that in the IBM ecosystem OS and VM are the names of specific operating systems, rather than being a generic abbreviation; in addition DOS is a '60s-era IBM operating system with no connection to Microsoft or PCs.''<br />
<br />
VM/370, which is freely available as the "SixPack" (currently v1.2 as of early 2013), provides a hypervisor running on the "bare metal" which can host multiple single- or multitasking operating systems such as the (provided) Conversational Monitor System (CMS) or derivatives of DOS or OS (e.g. VSE or MVS).<br />
<br />
The CMS interactive environment is a distant cousin to unix, and is probably usable by anybody who remembers MS-DOS or older operating systems; the "SixPack" includes extensive but not complete online help. There is no networking API exposed to user programs: code and data may be moved between the host computer (running Hercules) and guest sessions by mounting files simulating tape devices, by simulations of an 80-column card punch and reader, or by a simulation of a line printer.<br />
<br />
In common with other IBM operating systems of the era, the documented API is in the form of macros for an IBM-compatible assembler; other types of interface are available including diag codes for communication between CMS and VM, and virtualised hardware access using Channel Control Words (CCWs). The services provided are roughly comparable with MS-DOS v1 or CP/M, i.e. there are separate sets of macros for handling different classes of peripherals: do not expect to open the terminal, card reader or printer as a file. Particularly if the S/380 hack (see below) is applied, the GCC compiler and standard libraries may be used but most software development and maintenance is done using assembler.<br />
<br />
== IBM S/370, S/390, and the S/380 "hack" ==<br />
<br />
''This section is an even sketchier outline.''<br />
<br />
As discussed elsewhere, the S/360 and S/370 architectures are limited to 24-bit addresses while the S/390 allows 31-bit addresses without, in general, breaking compatibility. There is an unofficial extension of the Hercules emulator (frowned upon by purists) that implements a non-standard "S/380" architecture and modifies the most-recent freely-available IBM mainframe operating systems (VSE nee DOS, MVS nee OS, and VM/370) to exploit this extension. Using this, there is sufficient available memory to run a large-scale compiler such as GCC ''natively'' on one of the classic IBM operating systems, with the important caveats that only one program can use this facility at a time (i.e. while a 31-bit GCC and a 24-bit make should work, two copies of the 31-bit GCC won't), and that in the case of VM one program means one program per computer rather than one program per virtualised system/login.<br />
<br />
To make use of this, you need the Hercules-380 patch from [http://mvs380.sourceforge.net/], a classic operating system such as the VM/CMS "sixpack" from [http://vm370.31bits.net/vm370sixpack-1_2.zip], and possibly the MECAFF enhancement for additional terminal sessions and the IND$FILE program. In practice, it is impossible to do any of this without joining Yahoo!, and subscribing to the Hercules-VM370, hercules-os380 and H390-VM groups.<br />
<br />
== IA-64 (Itanium, Merced, McKinley etc.) ==<br />
<br />
''Terminally sketchy.''<br />
<br />
The FPC port targeting IA-64 exists in absolutely minimal form, i.e. a few skeleton files and that's about it. Since the IA-64 architecture appears to be heading in the same direction as its predecessor the iAPX-432, it's highly questionable whether any more work will be done on this architecture. However, for completeness (and because this author wants to be able to inspect some Itanium binaries)...<br />
<br />
While a few systems turn up on eBay etc., the asking price tends to be dictated by what the seller paid a few years ago rather than by what anybody expects to pay today. There is a simulator, written by HP, called "Ski", which is now open source. See [http://www.gelato.unsw.edu.au/IA64wiki/SkiSimulator], [http://www.hpl.hp.com/research/linux/ski/download.php] plus the Sourceforge project repository.<br />
<br />
(On a fairly complete development system running Debian) get the most recent Ski sources from Sourceforge. Run autogen.sh, installing e.g. autoconf, libtool and libelf-dev if necessary. Run make, install additional libraries such as gperf, bison, flex etc. as necessary. It might be necessary to edit syscall-linux.c to comment out the reference to asm/page.h which apparently isn't needed for very recent (Linux 3.x) kernels. On success get root and run make install.<br />
<br />
Assuming the disc image is a file named /export/ia-64/sda (e.g. renamed from the downloaded sda-debian) then<br />
<br />
bski bootloader vmlinux simscsi=/export/ia-64/sd<br />
<br />
Note the dropped final letter, that parameter is used as a prefix rather than being the name of a file or directory.<br />
<br />
As with a number of other emulators described on this page, this requires an X session for the console.<br />
<br />
This writer (MarkMLl) can't get networking running, either using the instructions at [http://www.hpl.hp.com/research/linux/ski/HOWTO-ski-networking.php] or using a tun/tap solution very roughly as suggested by [http://www.gelato.unsw.edu.au/IA64wiki/SkiVirtualNetwork]. It might be necessary to try a much older host kernel, i.e. late 2.4 or early 2.6, however this is not currently being pursued due to lack of relevance.<br />
<br />
The kernel from HP is 2.4.20, the filesystem is described as "Debian Sid" but from the kernel version is probably somewhere between "Woody" (3.0) and "Sarge" (3.1). I don't know how easy it's going to be to get this up to scratch, at the very least a working network is going to be a prerequisite.<br />
<br />
----<br />
<br />
== Relative Performance ==<br />
<br />
Performance is, in general, disappointing. Using an informal "torture test" which exercises the CPU and disc access:<br />
<br />
Compaq ProLiant ML530 G2, 2.8GHz, 3Gb, 8 jobs, 390W 0m12.170 79<br />
<br />
Linksys NSLU2, 266MHz 32Mb, 1 job, 7W 6m35.014s 46<br />
<br />
Qemu ARM, 1 job, 390W 42m58.925s 16,757<br />
<br />
Qemu MIPS, 1 job, 390W 17m49.103s 6,949<br />
<br />
Qemu x86, 1 job, 390W 47m0.441s 18,330<br />
<br />
UML, 1 job, 390W 8m26.529s 3,289<br />
<br />
Hercules zSeries, 4 jobs, 390W 9m43.330s 3,790<br />
<br />
The final column in the above list is the W-Mins to complete the job. These timings are without the benefit of kernel support from kqemu (obsolete) or KVM, but the fact that Qemu's support for MIPS is significantly better than that for other architectures, and the fact that the Hercules emulator is in all cases better than Qemu, does make one wonder how efficient the code is.<br />
<br />
Compiling FPC 2.4.2 using time make NOGDB=1 OPT='-O- -gl' all :<br />
<br />
Host (x86, Linux, Debian):<br />
<br />
real 4m47.842s<br />
user 3m42.126s<br />
sys 0m33.506s<br />
<br />
Slug (32Mb, ARM, Linux, Debian):<br />
<br />
real 284m58.543s<br />
user 86m45.570s<br />
sys 20m46.500s<br />
<br />
Qemu (ARM, Linux, Debian):<br />
<br />
real 406m31.931s<br />
user 236m49.030s<br />
sys 148m58.110s<br />
<br />
Qemu (x86, Linux, Slackware):<br />
<br />
real 141m45.700s<br />
user 122m40.724s<br />
sys 17m15.670s<br />
<br />
Qemu (x86, Windows 2000):<br />
<br />
Elapsed 108m<br />
<br />
UML (x86, Linux, Slackware):<br />
<br />
real 238m41.257s<br />
user 45m54.460s<br />
sys 3m44.140s<br />
<br />
Compiling Lazarus 0.9.30 using time make LCL_PLATFORM=gtk2 bigide :<br />
<br />
Host (x86, Linux, Debian):<br />
<br />
real 2m21.072s<br />
user 2m6.452s<br />
sys 0m12.285s<br />
<br />
Slug (32Mb, ARM, Linux, Debian):<br />
<br />
real 9385m49.319s<br />
user 67m23.460s<br />
sys 430m9.870s<br />
<br />
Qemu (ARM, Linux, Debian):<br />
<br />
real 281m55.536s<br />
user 153m3.150s<br />
sys 53m27.470s<br />
<br />
Qemu (x86, Linux, Slackware):<br />
<br />
real 70m53.957s<br />
user 60m3.474s<br />
sys 8m8.801s<br />
<br />
Qemu (x86, Windows 2000, default platform):<br />
<br />
Elapsed 53m<br />
<br />
UML (x86, Linux, Slackware):<br />
<br />
real 489m40.233s<br />
user 81m43.740s<br />
sys 7m51.280s<br />
<br />
== Graphical Access using VNC etc. ==<br />
<br />
All of the above assumes that SSH is available so that shell (command line) sessions can be run over the network. Where necessary install e.g. the Debian ssh (or openssh, openssh-server etc.) package. It is also possible to run a program using a GUI over SSH by X forwarding, see the SSH client's -X option and the corresponding server configuration-file settings.<br />
<br />
There are several ways of running a program using a GUI without forwarding the X protocol over SSH:<br />
<br />
* Running Qemu or UML in an xterm.<br />
<br />
* Using Qemu's host VNC support.<br />
<br />
* Connecting to a Qemu (or UML etc.) guest using X.<br />
<br />
* Connecting to a Qemu (or UML etc.) guest using VNC.<br />
<br />
In all cases it is worth using a low-resource window manager such as FluxBox or xfwm rather than KDE or Gnome.<br />
<br />
=== Running Qemu or UML in an xterm or with host VNC support ===<br />
<br />
This depends on the build options of the guest system kernel etc. In general, display resolution and depth will be limited by the emulated hardware, the network ports are associated with the host (rather than guest) system. See the Qemu documentation for details.<br />
<br />
=== Connecting to a Qemu (or UML etc.) guest using X ===<br />
<br />
Install the Gnome Display Manager (gdm) on the guest system. This allows a desktop system to log into it using XDMCP, the desktop system determines the display resolution and depth.<br />
<br />
=== Connecting to a Qemu (or UML etc.) guest using VNC ===<br />
<br />
This method also works well for arbitrary systems, even if Qemu etc. is not being used.<br />
<br />
Install gdm and RealVNC (or, with limitations, TightVNC) on the guest system; the latter might be needed for ARM guests but RealVNC is to be preferred when available. This allows a desktop system to log into it using VNC, the guest system determines the display resolution and depth and network ports are associated with the guest (rather than host) system.<br />
<br />
In /etc/inittab, insert something like this:<br />
<br />
6:23:respawn:/sbin/getty 38400 tty6<br />
<br />
# Added in lieu of trying to get it working in inetd.conf. MarkMLl.<br />
<br />
8:23:respawn:/usr/local/vnc/vncshim-0-20<br />
# 9:23:respawn:/usr/local/vnc/vncshim-4-24<br />
<br />
# Example how to put a getty on a serial line (for a terminal)<br />
#<br />
#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100<br />
<br />
Many systems limit the number of VNC servers that may be run per system (including localhost) as the side effect of a window manager XDMCP security precaution. If necessary, edit the window manager configuration file (gdm.conf, kdmrc etc.) to increase the number of sessions that each XDMCP client can request, e.g. in the case of gdm.conf:<br />
<br />
[xdmcp]<br />
Enable=true<br />
DisplaysPerHost=6<br />
<br />
Create /usr/local/vnc/vncshim-0-20 to read:<br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
<br />
# This forms a shim between /etc/inittab and the VNC server, necessitated by<br />
# the facts that (a) I can't get this command working reliably in inetd.conf<br />
# and (b) lines in initab have restricted length.<br />
#<br />
# Note that the name of the file reflects the fact that it might request non-<br />
# standard combinations of VNC port and X display number. MarkMLl.<br />
<br />
# My preference is to select default linear dimensions that appear somewhere<br />
# in the list of conventional graphics modes. 1200x960 fits inside a 1280x1024<br />
# screen, even allowing that the title bar etc. will take additional space.<br />
<br />
SIZE=1200x960<br />
<br />
# The DPI setting can usefully be set to match the user's normal screen, leave<br />
# undefined (commented out) if in doubt.<br />
<br />
DPI='-dpi 96'<br />
<br />
# Each VNC server running on a host must have a distinct suffix to prevent port<br />
# and display number clashes.<br />
<br />
SUFFIX=0<br />
<br />
# Some option formats vary between RealVNC (top) and TightVNC (bottom).<br />
<br />
OPTIONS='DisconnectClients=0 -NeverShared'<br />
# OPTIONS='-dontdisconnect -nevershared'<br />
<br />
####################### CHANGE NOTHING BELOW HERE ###########################<br />
<br />
VNCPORT=570$SUFFIX<br />
XDISPLAY=:2$SUFFIX<br />
<br />
# Try to work out what this system is. Note particular care to allow spaces<br />
# in the desktop name.<br />
<br />
WHATAMI=`dirname $0`/whatami<br />
if [ -x $WHATAMI ]; then<br />
WHATAMI2=`$WHATAMI`<br />
else<br />
if [ -r $WHATAMI ]; then<br />
WHATAMI2=`cat $WHATAMI`<br />
else<br />
WHATAMI2=$$<br />
fi<br />
fi<br />
DESKTOP='-desktop '\'$WHATAMI2\'<br />
<br />
# Optional Java viewer.<br />
<br />
if [ -r `dirname $0`/classes/index.vnc ]; then<br />
JAVA="-httpd `dirname $0`/classes -httpPort 580$SUFFIX"<br />
fi<br />
<br />
# This is the only way I can get it working the way I want, i.e. with reliable<br />
# handling of spaces in the desktop name. I'm hardly proud of it.<br />
<br />
rm -f `dirname $0`/.vncshim-$SUFFIX-1$SUFFIX<br />
<br />
cat >`dirname $0`/.vncshim-$SUFFIX-1$SUFFIX <<EOT<br />
#!/bin/sh<br />
exec /usr/bin/Xvnc -geometry $SIZE $DPI -depth 16 $DESKTOP \<br />
-rfbport $VNCPORT -rfbauth /root/.vnc/passwd-$SUFFIX $JAVA \<br />
$OPTIONS -query localhost -once $XDISPLAY<br />
EOT<br />
<br />
chmod +x `dirname $0`/.vncshim-$SUFFIX-1$SUFFIX<br />
exec `dirname $0`/.vncshim-$SUFFIX-1$SUFFIX<br />
</syntaxhighlight><br />
<br />
Create /usr/local/vnc/whatami to read:<br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
<br />
echo -n "`hostname`, "<br />
_WHATAMI=`uname -m`<br />
if [[ $_WHATAMI == i?86 ]]; then<br />
_WHATAMI='i586'<br />
fi<br />
if [[ $_WHATAMI == arm* ]]; then<br />
_WHATAMI='arm'<br />
fi<br />
<br />
if [ -x `dirname $0`/whatami-$_WHATAMI ]; then<br />
`dirname $0`/whatami-$_WHATAMI<br />
else<br />
uname -m<br />
fi<br />
</syntaxhighlight><br />
<br />
Additional scripts e.g. whatami-arm can query /proc/cpuinfo, which is different for each architecture. Make sure that all scripts are set +x for root.<br />
<br />
Use vncpasswd to set up password files /root/.vnc/passwd-0 etc.<br />
<br />
So far, I find that RealVNC works fine when invoked as above, but TightVNC doesn't correctly invoke the display manager.<br />
<br />
== Accessing "Unusual" Devices ==<br />
<br />
This section covers devices such as audio (i.e. used for playing .wav files) and MIDI, and USB devices which are not claimed by the host or guest kernel.<br />
<br />
=== Sound/MIDI for Qemu ===<br />
<br />
Don't expect this to be hi-fi quality, but it should be adequate for operator alerts etc.<br />
<br />
==== Identifying the Host's Hardware ====<br />
<br />
Obviously, a good starting point for this is using lspci and lsusb to determine what physical hardware is installed. The next stage is usually to consult dmesg output and to use lsmod to determine what subsystems the kernel has loaded hence what APIs are available. Graphical tools such as the KDE Info Centre can also be useful here, however identifying which API is most useful can be a bit of a black art.<br />
<br />
The current writer (MarkMLl) has a Debian "Squeeze" host with USB-connected audio and MIDI devices, with the latter connected to a Yamaha tone generator. An alternative (and more conventional) configuration would be to have an internal soundcard, with both audio and MIDI output feeding a loudspeaker.<br />
<br />
Knowing what hardware and subsystems are available, the remaining question is which subsystems Qemu can make use of. Running this command goes some way towards providing an answer:<br />
<br />
$ qemu -audio-help |grep ^Name:<br />
Name: alsa<br />
Name: oss<br />
Name: sdl<br />
Name: esd<br />
Name: pa<br />
Name: none<br />
Name: wav<br />
<br />
For reasons that are unclear ''(roughly translated: I'd like some help here)'' I've had most success with OSS, starting Qemu with a script that includes these lines:<br />
<br />
<syntaxhighlight lang="bash"><br />
export QEMU_AUDIO_DRV=oss<br />
export QEMU_OSS_DAC_DEV=/dev/dsp1<br />
export QEMU_OSS_ADC_DEV=/dev/dsp1<br />
</syntaxhighlight><br />
<br />
==== Selecting the Guest's Soundcard ====<br />
<br />
Assuming that the guest is to have both audio and MIDI, common sense would suggest that the guest operating system should see both audio and MIDI hardware implemented by Qemu. According to http://rubenerd.com/qemu-ad-lib-midi-win-31/, this implies that Qemu should be recompiled with the capability of emulating an Adlib card, which for current versions of Qemu means running something like this:<br />
<br />
<syntaxhighlight lang="bash"><br />
make clean distclean<br />
./configure --audio-card-list=ac97,es1370,sb16,adlib<br />
make<br />
</syntaxhighlight><br />
<br />
Irrespective of whether a custom Qemu is built or not, it's useful to check what devices it emulates:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ qemu -soundhw ?<br />
Valid sound card names (comma separated):<br />
pcspk PC speaker<br />
sb16 Creative Sound Blaster 16<br />
ac97 Intel 82801AA AC97 Audio<br />
es1370 ENSONIQ AudioPCI ES1370<br />
</syntaxhighlight><br />
<br />
For some guest operating systems (e.g. Windows NT 4), adding Adlib emulation to Qemu is sufficient to get MIDI working with the Sound Blaster, i.e.<br />
<br />
<syntaxhighlight lang="bash">$ qemu ... -soundhw sb16,adlib ...</syntaxhighlight><br />
<br />
In this case the guest uses only the standard Sound Blaster driver, possibly ignoring MPU-401 support.<br />
<br />
For guest systems which don't benefit from this, there's little point adding something like an Adlib card if the guest operating system doesn't have a driver for it- which appears to be the case with Windows 2000. The current writer's preference is to select the ES13770, since as a straightforward PCI device it should be enumerated without difficulty by almost any guest operating system:<br />
<br />
<syntaxhighlight lang="bash">$ qemu ... -soundhw es1370 ...</syntaxhighlight><br />
<br />
Having started Qemu, it's possible to query the emulated devices:<br />
<br />
(qemu) info pci<br />
...<br />
Bus 0, device 4, function 0:<br />
Audio controller: PCI device 1274:5000<br />
IRQ 11.<br />
BAR0: I/O at 0xc200 [0xc2ff].<br />
id ""<br />
<br />
The expected result of this is that the guest operating system would see audio hardware, but not MIDI. However in the case of Windows 2000 a MIDI device is emulated, so programs which use MIDI for e.g. operator alerts will work properly.<br />
<br />
''Anybody: need help here generalising this to other guest operating systems.''<br />
<br />
=== USB for Qemu ===<br />
<br />
As an example, a Velleman K8055 board is plugged into the host. First use lsusb to gets its vid:pid identifiers:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ lsusb<br />
Bus 001 Device 005: ID 10cf:5500 Velleman Components, Inc. 8055 Experiment Interface Board (address=0)<br />
</syntaxhighlight><br />
<br />
Now add these parameters to the Qemu command line:<br />
<br />
<syntaxhighlight lang="bash">qemu ... -usb -usbdevice host:10cf:5500 ...</syntaxhighlight><br />
<br />
On the guest, lsusb should show the device available for use.<br />
<br />
The Velleman board is unusual in that it has ID jumpers which set the last digit, i.e. multiple devices appear as 10cf:5500 through 10cf:5503. In cases where this facility is not available, the bus and device number can be used:<br />
<br />
<syntaxhighlight lang="bash">qemu ... -usb -usbdevice host:1.5 ...</syntaxhighlight><br />
<br />
Obviously, there's a risk here that devices will move around between invocations.<br />
<br />
=== USB for UML ===<br />
<br />
There was supposedly a patch that mapped unused USB devices on the host into the guest. I'm not sure it made it into the standard release.<br />
<br />
== QEMU User Emulation Mode in Chrooted Environment ==<br />
<br />
In previous chapters QEMU's full system emulation mode was described: a complete guest system is emulated including bios and hardware. Although this approaches a real environment as close as possible, the overhead created by emulating a complete system is considerable. QEMU's user emulation mode allows to run "guest" code directly on the "host" system. The cpu is still emulated but system calls are translated into system calls on the host system. Libraries used by the "guest" code are the "guest" libraries, not the host libraries. To run a small program created for fe. ARM with no dependencies you can simply do a <br />
$ qemu-arm program<br />
When the program has more and more dependencies, it becomes increasingly difficult to put all dependencies in locations that can be found by qemu-arm without messing up your host system. The solution that will be developed here is to create a chroot environment on the host in which all code is "guest" code. Advantage of this solution: <br />
*the chroot isolates the guest from the host <br />
*no need for vnc, ssh or any other solution to export screens. <br />
*no booting of a guest OS system.<br />
*guest "immersion" for user programs is almost complete. Only very low level and hardware related programming will notice the difference.<br />
*a spectacular speed improvement for graphics (X) related tasks (running lazarus f.e.) <br />
*hard disk resources are common to host and guest. Host can read and modify "guest" files but not the other way around.<br />
<br />
<u>Setting up the system.</u><br />
<br />
For a better comprehension of what follows, here is the directory structure used in this how-to:<br />
<br />
home<br />
qemu<br />
arm-linux-user<br />
sparc-linux-user<br />
scripts <br />
disks // here are qemu images for the different systems emulated<br />
chroot<br />
mount <br />
arm // here we'll chroot the arm environment<br />
sparc <br />
<br />
Get and build the latest qemu<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd <br />
$ git clone git://git.savannah.nongnu.org/qemu.git qemu<br />
$ cd qemu<br />
</syntaxhighlight><br />
<br />
Build all emulations (full system and user emulation mode, all processors) <br />
<syntaxhighlight lang="bash">$ ./configure --static to build </syntaxhighlight><br />
<br />
or only the user emulations needed <br />
<br />
<syntaxhighlight lang="bash">$ ./configure --static --target-list=sparc-linux-user,arm-linux-user<br />
<br />
$ make clean all<br />
</syntaxhighlight><br />
<br />
The --static option makes that qemu-arm and qemu-sparc are linked statically. Since they will be running in the chroot environment later on, they must be build without dependencies. Some distributions provide already statically linked binaries. fe qemu-arm-static on debian. Although they are not the latest and greatest, using these packages makes it somewhat easier. Building QEMU from source is really just a matter of minutes.<br />
<br />
Now we need to copy the complete guest system into chroot/arm or chroot/sparc. There are several ways of doing this but when you don't have a real arm or sparc system at hand, using a QEMU image of a full system emulation as described in previous chapters is the easiest.<br />
<br />
Mount the QEMU image.<br />
<br />
WARNING: do not mount a qemu image when the associated guest is running!<br />
<br />
Only raw QEMU images can be mounted as a loop-back device. If you have a qcow formatted image, convert it to a raw image.<br />
<br />
<syntaxhighlight lang="bash"><br />
$ qemu-img convert disks/armel_hda.img -O raw disks/armel_hda.raw<br />
<br />
$ cd chroot <br />
$ su<br />
# mount ../disks/armel_hda.raw mount -o loop,offset=32256<br />
</syntaxhighlight><br />
<br />
The offset is the start of the first partition on the ARM disk when the default disk layout was used during installation. If mount fails then the offset is probably wrong. To calculate the offset:<br />
<br />
<syntaxhighlight lang="bash"># fdisk -lu ../disks/armel_hda.raw</syntaxhighlight><br />
<br />
Find the first sector of the partition that will be mounted as root. Calculate offset as (start sector number * 512). For the default SPARC debian etch installation using a sun partition table with a /boot first partition the offset is 98703360.<br />
<br />
Check if the correct partition is mounted:<br />
<br />
<syntaxhighlight lang="bash"># ls mount</syntaxhighlight><br />
<br />
Copy the complete system<br />
<br />
<syntaxhighlight lang="bash"><br />
# cp -r mount/* arm/<br />
# umount mount<br />
</syntaxhighlight><br />
<br />
Copy the QEMU arm emulator to the arm directory<br />
<br />
<syntaxhighlight lang="bash"><br />
# cd arm<br />
# cp ../../arm-linux-user/qemu-arm usr/local/bin<br />
</syntaxhighlight><br />
<br />
Our "guest" system is complete. Since we are sharing our "host" devices and system, we need to mount a few special directories:<br />
<br />
<syntaxhighlight lang="bash"><br />
# mount --bind /proc proc<br />
# mount --bind /dev dev<br />
# mount --bind /sys sys<br />
# mount --bind /tmp tmp<br />
</syntaxhighlight><br />
<br />
Why /tmp? The /tmp directory is used by the X window system. When you don't mount tmp on the host /tmp, you will get "can't open display 0:0" errors when trying to open a window in the guest.<br />
<br />
We are going to use the host network:<br />
<br />
<syntaxhighlight lang="bash"># sudo cp /etc/resolv.conf etc</syntaxhighlight><br />
<br />
Remains one last thing: the emulator is copied into the guest but how do we tell the system that the emulator needs to kick in when a "quest" elf is detected? This is done with binfmt_misc. A configuration script is included with QEMU that registers the QEMU user mode emulators with binfmt_misc: (on my system I had to do a chmod +x on the script)<br />
<br />
<syntaxhighlight lang="bash"># ../../scripts/qemu-binfmt-conf.sh</syntaxhighlight><br />
<br />
When you look at this script at the end of the binary data you'll notice that the emulator is registered as /usr/local/bin/qemu-arm. And that is where we put it a few lines ago.<br />
<br />
Launch the system:<br />
<br />
<syntaxhighlight lang="bash"># chroot .</syntaxhighlight><br />
<br />
If all goes well:<br />
<br />
<syntaxhighlight lang="bash"><br />
# uname -m<br />
armv7l<br />
</syntaxhighlight><br />
<br />
Everything you do from here is in your "guest" system. <br />
To exit the "guest" system.<br />
<br />
<syntaxhighlight lang="bash"># exit</syntaxhighlight><br />
<br />
And unmount the special directories.<br />
<br />
<syntaxhighlight lang="bash"><br />
# umount proc<br />
# umount dev<br />
# umount sys<br />
# umount tmp<br />
</syntaxhighlight><br />
<br />
<u>Conclusion.</u><br />
<br />
The ARM emulation works very well, except for gdb. On the contrary there are still quite some problems with the SPARC emulation. There is a bug in duplicating pipes which makes the use of bash impossible. The sh shell works but several unix commands don't function correctly. What works fine is fpc and lazarus! Programs can't be launched from inside lazarus. Because of the improved graphics speed when using the user emulation mode it is still worthwhile for using lazarus. Instead of starting a shell in chroot, you start directly lazarus:<br />
<br />
<syntaxhighlight lang="bash">$ sudo chroot . /your/lazarus/dir/lazarus</syntaxhighlight><br />
<br />
To run the program compiled in lazarus, you can launch it from another "host" terminal:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd qemu/chroot/arm<br />
$ sudo chroot . /your/program/dir/project<br />
</syntaxhighlight><br />
<br />
Here also gdb is not working.<br />
<br />
== Docker images ==<br />
<br />
Docker uses various APIs provided by the underlying operating system to isolate programs in separate namespaces.<br />
<br />
The implication of this is that every Docker container has its own process space (i.e. processes in different containers might have the same numeric PID), network space (processes in different containers can have different routing, firewall rules and so on), filesystems (except that they share kernel files and a number of crucial libraries and configuration files) and so on.<br />
<br />
Containerisation is distinct from virtualisation in that all containers which are running on the same host computer make use of the same host kernel, it is not possible for one container to use a different kernel (e.g. for development or deployment test purposes), and there is no absolute guarantee that a container taken over by malicious code cannot subvert the remainder of the system.<br />
<br />
'''''Note that there are pitfalls for the unwary. For example, having Docker installed and the Docker daemon running can break Qemu networking if this expects to have bridged access to the host interface rather than to be sitting on its own private subnet.'''''<br />
<br />
One thing that does work however, is running a 32-bit container on a 64-bit host, and in most cases the Linux kernel APIs are sufficiently backwards-compatible that the programs and libraries from an older release of a distro can run on a much more recent host. One of the cases which will be discussed here runs a container containing 32-bit Debian "Lenny" from 2008 on a host running Debian "Buster" from 2019, this combination allows e.g. compatibility with the GTK v1 widget set (no longer supported by more recent distreaux) to be tested.<br />
<br />
=== Docker fundamentals ===<br />
<br />
This is not the place for a full discussion of the care and feeding of Docker, but it's worth establishing a few basic concepts.<br />
<br />
* Host: the physical computer running e.g. Qemu (with or without KVM) or Docker.<br />
<br />
* Guest: on a virtualised system (Qemu etc.), a completely separate memory space with its own kernel.<br />
<br />
* Container: on a Docker based system, a group of application programs sharing the host kernel but largely isolated from the rest of the host system using various namespace mechanisms.<br />
<br />
Looking specifically at Docker:<br />
<br />
* Dockerfile: a succinct description of what is to go into a Docker image.<br />
<br />
* Image: the Docker analogue of a bootable filesystem, initially built with reference to a Dockerfile.<br />
<br />
* Container: an image which has been run, whether or not it is currently running.<br />
<br />
Once running, a container has an associated parameter list which specifies the main program together with any sockets etc. exposed to the host. Neither an image nor a container correspond to a single file on the host, but:<br />
<br />
* A container may be stopped and committed to an image.<br />
<br />
* An image may be saved to a single file, in which state it can be moved between hosts.<br />
<br />
* An image may be run a set of parameters different from those of the container committed to it.<br />
<br />
The result of this is that if something is installed in the context of a container (e.g. Apache is added from the operating system's main repository) the container should be stopped, committed to a new image and then run with an additional parameter specifying what sockets etc. should be exposed. There are ways around that, but that combination appears to be the easiest to manage.<br />
<br />
The Docker community has a master repository, and the docker program on a host computer may search it to locate images. However in the context of those images there appears to be no "official" way of searching the tags that allow a specific version to be pulled, hence something "messy" and subject to change like<br />
<br />
<pre><br />
$ docker search debian<br />
<br />
$ wget -q https://registry.hub.docker.com/v1/repositories/debian/tags -O - \<br />
| sed -e 's/[][]//g' -e 's/"//g' -e 's/ //g' | tr '}' '\n' | awk -F: '{print $3}' | grep buster<br />
<br />
$ docker pull debian:buster<br />
</pre><br />
<br />
or alternatively (for the really hard to reach bits)<br />
<br />
<pre><br />
$ docker search vicamo<br />
<br />
$ wget -q https://registry.hub.docker.com/v1/repositories/vicamo/debian/tags -O - \<br />
| sed -e 's/[][]//g' -e 's/"//g' -e 's/ //g' | tr '}' '\n' | awk -F: '{print $3}'<br />
<br />
...<br />
buster-i386<br />
buster-i386-sbuild<br />
buster-i386-slim<br />
...<br />
<br />
$ docker pull vicamo/debian:buster-i386-slim<br />
</pre><br />
<br />
Generally speaking, if a developer on e.g. Github says that he has a special-purpose image, some combination of the above commands will allow the required version (e.g. in the above example tagged buster-i386-slim) to be pulled.<br />
<br />
Neither a container nor an image is directly mountable e.g. by using losetup. It is possible to copy files to or from a container using the <code>docker container cp</code> command, subject to the same precautions that one would take on a running unix system.<br />
<br />
=== A 64-bit Docker container with basic utilities ===<br />
<br />
This is a basic installation of a 64-bit Debian stable release ("Buster" at the time of writing) on the same host version. This is useful in a situation where a developer wants to experiment with libraries etc. that he does not normally have installed, it should probably not be trusted to quarantine untrusted code.<br />
<br />
In the instructions below, commands on the host are run in the context of a normal user so are shown prefixed by a $ prompt, while commands run in the context of the container are run by root so shown prefixed by a # prompt.<br />
<br />
<pre><br />
$ docker pull debian:buster<br />
<br />
$ docker run --interactive --tty debian:buster<br />
<br />
# apt-get update<br />
<br />
# apt-get install dialog tasksel python3<br />
<br />
# tasksel --new-install<br />
<br />
# dpkg-reconfigure locales<br />
</pre><br />
<br />
Using tasksel, remove desktop and print server and add SSH. The author favours installing net-tools so that the <code>netstat -an</code> command can be used to check socket accessibility.<br />
<br />
<pre><br />
# apt-get install net-tools elvis-tiny<br />
</pre><br />
<br />
So far there is no provision for starting the SSH daemon. Add or edit /etc/rc.local to contain<br />
<br />
<pre><br />
#!/bin/sh<br />
<br />
rm -rf /tmp/*<br />
<br />
# For cases where a 32-bit container is running (by implication on a 64-bit host), make<br />
# sure that commands such as uname -a return consistent information to avoid breaking <br />
# the traditional ./configure && make operation.<br />
<br />
(file -L /bin/sh | grep -q 'ELF 32-bit LSB')<br />
if [ $? -eq 0 ] ; then<br />
LINUX32=linux32<br />
fi<br />
<br />
cd /etc/init.d<br />
$LINUX32 ./ssh start<br />
# $LINUX32 ./openbsd-inetd start<br />
<br />
cd<br />
exec $LINUX32 /bin/bash<br />
</pre><br />
<br />
The ID of the current container is in the prompt. Exit it and commit to a new image, then start with a parameter to expose the SSH socket:<br />
<br />
<pre><br />
# exit<br />
<br />
$ docker container commit 4efab4804fb6 debian_minimal<br />
<br />
$ docker run -it -p 65022:22 \<br />
--entrypoint=/etc/rc.local debian_minimal<br />
<br />
# groupadd -g 1000 markMLl<br />
<br />
# useradd -g markMLl -u 1000 -m -d /home/markMLl markMLl<br />
<br />
# passwd markMLl<br />
</pre><br />
<br />
Add newly created user name to sudo group (/etc/group and /etc/gshadow files). Note the container ID, if it is now stopped the parameters are preserved and it should be possible to start it in the background and connect as the above user via SSH.<br />
<br />
<pre><br />
$ docker container stop c5be7fd96866<br />
<br />
$ docker container start c5be7fd96866<br />
<br />
$ ssh localhost -p 65022<br />
</pre><br />
<br />
The above is a good starting point for a general-purpose containerised system. The section below will also show how to get a graphical program (e.g. the Lazarus IDE) running in a container.<br />
<br />
=== A 32-bit Docker container with FPC and Lazarus ===<br />
<br />
An understanding of the principles and commands described above is a prerequisite to continuing with this section.<br />
<br />
Unlike Qemu, a container will run at the host computer's full speed. Unlike User Mode Linux (UML), a 32-bit container will run on a 64-bit host. Subject to security issues, this makes Docker the preferred choice when setting up a temporary 32-bit environment to e.g. investigate libraries which would not normally be installed on the host.<br />
<br />
This section discusses installing a 32-bit Debian "Lenny" container, which allows a program to be checked with libraries etc. dating from roughly 2008.<br />
<br />
Referring to https://github.com/lpenz/docker-debian-releases/<br />
<br />
<pre><br />
$ docker pull lpenz/debian-lenny-i386<br />
<br />
$ docker image ls<br />
<br />
$ docker run --interactive --tty lpenz/debian-lenny-i386<br />
</pre><br />
<br />
Lenny authentication is broken and is not going to be fixed (short of forking the entire repository). In /etc/apt/apt.conf put<br />
<br />
<pre><br />
APT <br />
{<br />
// Options for apt-get<br />
Get <br />
{<br />
AllowUnauthenticated "true";<br />
}<br />
}<br />
</pre><br />
<br />
which should allow tasksel etc. to work. Add contrib and non-free to /etc/apt/sources.list, then<br />
<br />
<pre><br />
# apt-get update<br />
<br />
# apt-get upgrade<br />
<br />
# tasksel --new-install<br />
<br />
# dpkg-reconfigure locales<br />
<br />
# apt-get install openssh-server sudo<br />
</pre><br />
<br />
Populate /etc/rc.local as discussed above, comment out the temporary setting from /etc/apt/apt.conf<br />
<br />
Create group and user as discussed above, add user to sudo group.<br />
<br />
Add the build-essential, gdb, subversion and zip packages as basic prequisites. Add libncurses5-dev, libncursesw5-dev and libgpm-dev to allow FPC to be rebuilt, plus possibly libpango1.0-dev for the FP IDE. Install FPC v2.2.4 binary for i386 and corresponding sources, the compiler should run and be able to recompile itself.<br />
<br />
Install the libgtk1.2-dev package then Lazarus 0.9.24.1, don't attempt to run it yet. In the per-user shell startup file e.g. ~/.bashrc put the line<br />
<br />
export DISPLAY=:0<br />
<br />
then exit and commit the container as described above and restart with something like<br />
<br />
$ docker run -it -p 65022:22 -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \<br />
--entrypoint=/etc/rc.local lenny_with_lazarus<br />
<br />
Again, details as discussed in the section above.<br />
<br />
It should now be possible to login to the container from the host over SSH, invoke e.g. /usr/local/share/lazarus/lazarus by name, and have the IDE appear on the host computer's screen.<br />
<br />
== Further Reading ==<br />
<br />
* [http://www.aurel32.net/info/debian_arm_qemu.php Debian on an emulated ARM machine]<br />
* [https://forum.lazarus.freepascal.org/index.php/topic,56098.msg420159.html#msg420159 Debian 11 on an emulated ARM/ARM64 machine]<br />
* [http://www.aurel32.net/info/debian_mips_qemu.php Debian on an emulated MIPS(EL) machine]<br />
* [http://en.wikibooks.org/wiki/QEMU/Windows_XP QEMU/Windows XP on Wikibooks]<br />
* [http://user-mode-linux.sourceforge.net/ The User-mode Linux Kernel Home Page]<br />
* [http://www.josefsipek.net/docs/s390-linux/hercules-s390.html Installing Debian under Hercules]<br />
<br />
==See also==<br />
<br />
* [[Native ARM Systems]] <br />
* [[Native MIPS Systems]]<br />
* [[Small Virtual Machines]]<br />
* [[Docker Containerization]]<br />
<br />
[[Category: Emulators]]<br />
[[Category: Docker]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:Docker&diff=153483Category:Docker2022-08-16T15:29:53Z<p>Skalogryz: Created page with "Docker is the automatic application deployment system. Any pages related to that could be mentioned under this category"</p>
<hr />
<div>Docker is the automatic application deployment system. Any pages related to that could be mentioned under this category</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Official_Docker_Images&diff=153482Official Docker Images2022-08-16T15:28:36Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>== Introduction ==<br />
In the last years the use of containerization, often with Docker - the world's leading software container platform [https://www.geeksforgeeks.org/containerization-using-docker/], has grown rapidly due to the high usage of [https://www.redhat.com/en/topics/devops/what-is-ci-cd Continuous Integration / Continuous Delivery] (short: CI/CD) in the development process. There a multiple repositories on DockerHub that pretend to provide Docker files and images for FPC but none of them is 'official' and it's also unsure if they are still maintained. Often they also only provide images for a single architecture.<br />
<br />
To ease the local development within containers and the use of CI/CD with FPC - also on different architectures - the FPC team provides official Docker containers on [https://hub.docker.com/r/freepascal/fpc DockerHub] and [https://gitlab.com/freepascal.org/fpc/docker GitLab].<br />
<br />
<br />
<br />
=== Docker ===<br />
A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings. Container images become containers at runtime. Containers isolate software from its environment and ensure that it works uniformly despite differences for instance between development and staging. <br />
<br />
Docker containers run on Windows, Linux, macOS and cloud service providers like Amazon Web Services and Microsoft Azure.<br />
<br />
Applications that traditionally run as Unix daemons or Windows services, such as web servers and databases, increasingly run containerized. <br />
<br />
For more information and the difference between virtual machines and containers, see [https://www.docker.com/resources/what-container What is a Container? | App Containerization | Docker]<br />
<br />
=== Alpine Linux ===<br />
[https://alpinelinux.org/ Alpine Linux] is a security-oriented, lightweight Linux distribution based on musl libc and BusyBox. The image is only 5 MB in size and has access to a [https://pkgs.alpinelinux.org/packages package repository] that is much more complete than other BusyBox based images. [https://hub.docker.com/_/alpine]<br />
<br />
=== Ubuntu ===<br />
[https://ubuntu.com/ Ubuntu] is a Debian-based Linux operating system that runs from the desktop to the cloud. It is the world's most popular operating system across public clouds and OpenStack clouds. It is the number one platform for containers; from Docker to Kubernetes to LXD, Ubuntu can run your containers at scale. Fast, secure and simple, Ubuntu powers millions of PCs worldwide. [https://hub.docker.com/_/ubuntu]<br />
<br />
<br />
<br />
== Design considerations ==<br />
* The images can be pulled with the specific, stable tags, e.g. <syntaxhighlight lang=text inline>3.2.2-focal-full</syntaxhighlight> or <syntaxhighlight lang=text inline>3.2.2-slim</syntaxhighlight>, but also with the latest tag which will always point to the latest official FPC version. At the time of writing this article <syntaxhighlight lang=text inline>latest-focal-full</syntaxhighlight> points to <syntaxhighlight lang=text inline>3.2.2-focal-full</syntaxhighlight>, <syntaxhighlight lang=text inline>latest-slim</syntaxhighlight> to <syntaxhighlight lang=text inline>3.2.2-slim</syntaxhighlight>, etc.<br />
<br />
* Dockerfiles should be stored inside the GitLab FPC project and images be build automatically through the GitLab CI.<br />
<br />
* The Ubuntu version covers exactly what a user would install on their PC so using this Docker image should give the same user experience as a normal installation except that it's inside a container. This easily allows using different FPC versions without local installation conflicts.<br />
<br />
* FPC has support for many different architectures and platforms and thus also the Docker images should support as many as possible. Therefore we're using [https://docs.docker.com/buildx/working-with-buildx/ Docker Buildx] to provide multi-architecture Docker images. If you do <syntaxhighlight lang=text inline>docker pull freepascal/fpc:latest-full</syntaxhighlight> the image appropriate to your hardware architecture will be fetched by Docker. You can also force a platform via the [https://docs.docker.com/engine/reference/commandline/pull/ <syntaxhighlight lang=text inline>--platform</syntaxhighlight>] flag of <syntaxhighlight lang=text inline>docker pull</syntaxhighlight>. A list of available platform images can be seen on [https://hub.docker.com/r/freepascal/fpc/tags DockerHub]. Unfortunately the Gitlab Docker registry does [https://gitlab.com/gitlab-org/gitlab/-/issues/232815 not show multi-architecture images in the browser yet] but <syntaxhighlight lang=text inline>docker manifest inspect registry.gitlab.com/freepascal.org/fpc/docker/fpc:latest-full</syntaxhighlight> lists the same architectures as <syntaxhighlight lang=text inline>docker manifest inspect freepascal/fpc:latest-full</syntaxhighlight>.<br />
<br />
* The Dockerfiles makes extensive use of [https://docs.docker.com/develop/develop-images/multistage-build/ multi-stage builds] to reduce the size of each layer and to have everything within a single Dockerfile. Therefore the desired build stage of your own local container buil from the provided Dockerfile needs to be specified by the <syntaxhighlight lang=text inline>--target</syntaxhighlight> option. Otherwise the last stage inside the Dockerfile will be built.<br />
<br />
* For avoidance of splitting the Dockerfile content over multiple files the download script is generated as a separate stage and called later. Thus users need to use [https://docs.docker.com/buildx/working-with-buildx/ Docker Buildx] and run <syntaxhighlight lang=text inline>docker buildx build</syntaxhighlight> as we build multi-platform images.<br />
<br />
* The Alpine Linux images are provided because these images are always lightweight and it comes in different variants to save some additional space and bandwidth in case one knows that only a few specific things are needed to built its tool. This might also reflect the security-oriented approach of Alpine Linux so that nothing is shipped that isn't really used.<br />
** The full version provides every unit as well as binary but it comes without examples and documentation.<br />
** The slim version removes most of the packages and keeps only the core packages, known as RTL, and all binaries.<br />
** The minimal image includes all binaries and the part of the RTL that is needed to fully compile and test a newly built compiler.<br />
<br />
* Alpine Linux uses [https://musl.libc.org/ musl libc] as the C standard library as musl is lightweight, fast, simple, free, and strives to be correct in the sense of standards-conformance and safety. Therefore the image comes with the musl C library implementation (libc, libpthread, etc) and a lightweight glibc compatibility layer (<syntaxhighlight lang=text inline>libc6-compat</syntaxhighlight> package) to provide symlinks for libc, libpthread, etc. Additionally the GNU make utility <syntaxhighlight lang=text inline>make</syntaxhighlight> is needed to run the commands for building a new compiler, e.g. <syntaxhighlight lang=text inline>make fullcycle</syntaxhighlight> or <syntaxhighlight lang=text inline>make all</syntaxhighlight>.<br />
<br />
* To suppress several warnings of FPC, the Alpine Linux also provides the gcc binary and libgcc library that is fetched in the make script via gcc: <syntaxhighlight lang=text inline>gcc -print-libgcc-file-name</syntaxhighlight>.<br />
<br />
* SourceForge is unfortunately often quite slow or even timeouts during download (depends on the mirror they allocate you) but fortunately there is the [http://freemirror.org/ Free Mirror Project] which also provides the FPC tar files and is super-fast.<br />
<br />
* To allow updates or improvements to be applied for a specific version, each major version gets its own branch. But this has the negative side-effect that the <syntaxhighlight lang=text inline>latest</syntaxhighlight> might not point to the latest FPC release anymore. Thus you've to make sure that if something is merged into an older version, the pipeline for the newest version needs to be run again. '''Thus it's probably the simplest to avoid adding stuff to old FPC branches and to only maintain the recent major FPC version - the version the <syntaxhighlight lang=text inline>latest</syntaxhighlight> tag points to.'''<br />
<br />
* A newer version branch (e.g. 3.2.8) might base on a previous version branch (e.g. 3.2.6) but it doesn't be forked off from the nightly branch as those are fully independent.<br />
<br />
<br />
<br />
== TODO (help needed!) ==<br />
* Provide a Dockerfile that starts with the latest FPC image and builds a nightly version of the [https://gitlab.com/freepascal.org/fpc/source FPC Source main] branch via a [https://docs.gitlab.com/ee/ci/pipelines/schedules.html scheduled pipeline] inside a ''nightly'' branch. It might be enough to only provide a full version of the nightly build as the use of it is probably small and mostly for third-party developers testing their code against the development version of FPC without pulling and recompiling from source. The nightly builds should also be based on the nightly tags of Alpine Linux/Ubuntu.<br />
<br />
* Provide separate Dockerfiles for various combinations of cross-compilers inside the version branch (suggested naming convention: <syntaxhighlight lang=text inline><version>-<start_architecture>-to-<destination_architecture></syntaxhighlight>), e.g.:<br />
** 3.2.x-x86_64-to-win64 - image to compile from Linux x86_64 to Windows 64bit<br />
** 3.2.x-armv7-to-win64 - image to compile from Linux ARMv7 to Windows 64bit<br />
** 3.2.x-x86_64-to-aarch64 - image to compile from Linux x86_64 to Linux ARMv8 (AArch64)<br />
** ...<br />
<br />
* Create images that are based on the FPC ones but include Lazarus (maybe with (=Ubuntu) and without GUI part for CI (=Alpine)).<br />
<br />
* Specific Dockerfile for pas2js that can build projects but also images for secure deployment in the cloud.<br />
<br />
* Review (and fix?) the few other linting hints from [https://github.com/hadolint/hadolint Haskell Dockerfile Linter].<br />
<br />
* Verify if the QEMU install steps in the GitLab CI make a difference or if it's enough to run <syntaxhighlight lang=text inline>docker buildx create --use</syntaxhighlight> as described by [https://leimao.github.io/blog/Docker-Cross-Platform-Build/ Docker Cross-Platform Build].<br />
<br />
* Verify if really no official docker image supports the <syntaxhighlight lang=text inline>docker buildx</syntaxhighlight> command.<br />
<br />
* [https://docs.docker.com/engine/security/trust/ Sign the provided images] with the Docker Content Trust (DCT).<br />
<br />
* [https://docs.docker.com/get-started/09_image_best/ Scan the images for vulnerabilities] after pushing.<br />
<br />
* Consider [https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user adding and use a non-root user] as default.<br />
<br />
* Adjust download script to verify file against a provided sha256 checksum. ''Therefore the checksum must be provided for all official files by FPC first'' .<br />
<br />
* The GitLab CI scripts uses only a single stage as it's not possible yet to export the created multi-architecture image and the only way was to directly push it and then run the same command again to mirror it to another container registry. This also means it's not possible to test the newly created image before pushing it...<br />
<br />
<br />
<br />
== GitLab CI ==<br />
The <syntaxhighlight lang=text inline>.gitlab-ci.yml</syntaxhighlight> file checks if a Dockerfile exists to prevent jobs from running if no Dockerfile is provided for this distribution. The script uses the branch name to distinguish between different build flavors and create tags based on that. Therefore it is assumed that the default branch (=nightly) is used for nightly builds and versioned branch names are for dedicated FPC versions (regex for semantic versioning).<br />
<br />
For Ubuntu versions bumps (e.g. focal to jammy) the <syntaxhighlight lang=text inline>UBUNTU_VERSION</syntaxhighlight> inside the <syntaxhighlight lang=text inline>.gitlab-ci.yml</syntaxhighlight> file needs to be changed and not only the version argument <syntaxhighlight lang=text inline>ARG UBUNTU_VERSION=focal</syntaxhighlight> of the Dockerfile, otherwise the pull tags will be broken.<br />
<br />
<br />
<br />
== Further Reading ==<br />
* [https://developer.arm.com/documentation/102475/0100/Multi-architecture-images Arm Developer Documentation - Getting started with Docker]<br />
* [https://leimao.github.io/blog/Docker-Cross-Platform-Build/ Docker Cross-Platform Build]<br />
<br />
<br />
<br />
== See Also ==<br />
* [[Qemu and other emulators]]<br />
* [[Small Virtual Machines]]<br />
* [[Docker Containerization]]<br />
<br />
[[Category:Emulators]]<br />
[[Category:Docker]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Build_server&diff=153481Build server2022-08-16T15:27:32Z<p>Skalogryz: /* See also */</p>
<hr />
<div>Build server with automated build process is not useful just for big developers teams but also for single developer who working on multiple small applications. Build server can run daily or on-demand build for multiple projects and send notification emails if build was not successful. Build server can also help with cross-platform projects where developer would need to run build for all supported builds manually.<br />
<br />
=Setup on Ubuntu=<br />
<br />
Assume 64-bit Ubuntu server.<br />
<br />
Install binutils for required targets:<br />
<syntaxhighlight lang="bash"><br />
sudo apt-get install binutils-powerpc-linux-gnu<br />
sudo apt-get install binutils-powerpc64le-linux-gnu<br />
sudo apt-get install binutils-arm-linux-gnueabi<br />
</syntaxhighlight><br />
<br />
Create '''ld''' and '''as''' scripts needed for FPC:<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
<br />
cat >/usr/bin/i386-linux-ld << EOF<br />
#!/bin/bash<br />
ld -A elf32-i386 $@<br />
EOF<br />
chmod +x /usr/bin/i386-linux-ld<br />
<br />
cat >/usr/bin/i386-linux-as << EOF<br />
#!/bin/bash<br />
as --32 $@<br />
EOF<br />
chmod +x /usr/bin/i386-linux-as<br />
<br />
cat >/usr/bin/arm-linux-ld << EOF<br />
#!/bin/bash<br />
/usr/bin/arm-linux-gnueabi-ld $@<br />
EOF<br />
chmod +x /usr/bin/arm-linux-ld<br />
<br />
cat >/usr/bin/arm-linux-as << EOF<br />
#!/bin/bash<br />
/usr/bin/arm-linux-gnueabi-as $@<br />
EOF<br />
chmod +x /usr/bin/arm-linux-as<br />
<br />
cat >/usr/bin/powerpc-linux-ld << EOF<br />
#!/bin/bash<br />
/usr/bin/powerpc-linux-gnu-ld $@<br />
EOF<br />
chmod +x /usr/bin/powerpc-linux-ld<br />
<br />
cat >/usr/bin/powerpc-linux-as << EOF<br />
#!/bin/bash<br />
/usr/bin/powerpc-linux-gnu-as $@<br />
EOF<br />
chmod +x /usr/bin/powerpc-linux-as<br />
</syntaxhighlight><br />
<br />
=See also=<br />
<br />
* [[Jenkins]]<br />
<br />
[[Category:Cross compilation]]<br />
[[Category:CI/CD]]</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Category:CI/CD&diff=153480Category:CI/CD2022-08-16T15:26:58Z<p>Skalogryz: Created page with "any page that's related to Continues Integration and/or Continues Delivery"</p>
<hr />
<div>any page that's related to Continues Integration and/or Continues Delivery</div>Skalogryzhttps://wiki.freepascal.org/index.php?title=Docker_Containerization&diff=153479Docker Containerization2022-08-16T15:24:56Z<p>Skalogryz: /* See Also */</p>
<hr />
<div>== Docker ==<br />
<br />
A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings. Container images become containers at runtime. Containers isolate software from its environment and ensure that it works uniformly despite differences for instance between development and staging. <br />
<br />
Docker containers run on Windows, Linux, macOS and cloud service providers like Amazon Web Services and Microsoft Azure.<br />
<br />
Applications that traditionally run as Unix daemons or Windows services, such as web servers and databases, increasingly run containerized.<br />
<br />
== Alpine Linux ==<br />
<br />
Alpine Linux is a security-oriented, lightweight Linux distribution. The minimal Docker container image based on Alpine Linux is 5MB in size. <br />
<br />
== fcl-web example - HelloHeaders ==<br />
<br />
This example program responds to a web request by sending back information about the request. <br />
<br />
<syntaxhighlight lang=pascal>program HelloHeadersPure;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
cthreads, httpdefs, httproute, webutil,<br />
fphttpapp;<br />
<br />
procedure doEchoRequest(aReq: TRequest; aResp: TResponse);<br />
begin<br />
DumpRequest(aReq, aResp.contents, true);<br />
end;<br />
<br />
begin<br />
HTTPRouter.registerRoute('*', @doEchoRequest);<br />
Application.Port:=8080;<br />
Application.Threaded:=True;<br />
Application.Initialize;<br />
Application.Run;<br />
end.</syntaxhighlight><br />
<br />
== Dockerizing HelloHeaders ==<br />
<br />
Compile the example program on Ubuntu. Let's call the executable <syntaxhighlight lang=text inline>helloheaders</syntaxhighlight>. Below Dockerfile is used to create a Docker image for <syntaxhighlight lang=text inline>helloheaders</syntaxhighlight>.<br />
<br />
=== Dockerfile ===<br />
<br />
<pre><br />
# Start with the Alpine 3.12 Docker image.<br />
FROM alpine:3.12<br />
<br />
# Install libc6-compat, required for executables generated by FPC.<br />
RUN apk --no-cache --update add libc6-compat<br />
<br />
# Set working directory in the container image.<br />
WORKDIR /app<br />
<br />
# Copy the executable from host into container image.<br />
COPY helloheaders /app/helloheaders<br />
<br />
# Add uid/gid to run the binary. Don't want to run as root.<br />
RUN addgroup -g 1099 apprunner \<br />
&& adduser -D -u 1099 -G apprunner -h /home/apprunner apprunner<br />
<br />
# Set uid/gid for the running program.<br />
USER apprunner:apprunner<br />
<br />
# Make the TCP port 8080 available to outside the container.<br />
EXPOSE 8080<br />
<br />
# Run the program.<br />
CMD ["/app/helloheaders"]<br />
</pre><br />
<br />
=== Build Docker Image ===<br />
<br />
<pre>% sudo docker build -t helloheaders:fpc .<br />
Sending build context to Docker daemon 1.331MB<br />
Step 1/8 : FROM alpine:3.12<br />
---> a24bb4013296<br />
Step 2/8 : RUN apk --no-cache --update add libc6-compat<br />
---> Using cache<br />
---> 8bd9ae66e9fe<br />
Step 3/8 : WORKDIR /app<br />
---> Using cache<br />
---> 1c69cb1c14e3<br />
Step 4/8 : COPY helloheaders /app/helloheaders<br />
---> de5ffddf76bb<br />
Step 5/8 : RUN addgroup -g 1099 apprunner && adduser -D -u 1099 -G apprunner -h /home/apprunner apprunner<br />
---> Running in 54199f0a8f4f<br />
Removing intermediate container 54199f0a8f4f<br />
---> c7d18145bc02<br />
Step 6/8 : USER apprunner:apprunner<br />
---> Running in 0a544c5fc8f6<br />
Removing intermediate container 0a544c5fc8f6<br />
---> a3868d1f2033<br />
Step 7/8 : EXPOSE 8080<br />
---> Running in 829a1b804c0d<br />
Removing intermediate container 829a1b804c0d<br />
---> 7389810c7acf<br />
Step 8/8 : CMD ["/app/helloheaders"]<br />
---> Running in 882e002e08b0<br />
Removing intermediate container 882e002e08b0<br />
---> 399269970984<br />
Successfully built 399269970984<br />
Successfully tagged helloheaders:fpc</pre><br />
<br />
=== Check Size of Docker Image ===<br />
<br />
<syntaxhighlight lang=bash>% sudo docker images<br />
REPOSITORY TAG IMAGE ID CREATED SIZE<br />
helloheaders fpc 399269970984 2 minutes ago 7.51MB</syntaxhighlight><br />
<br />
=== Run Container ===<br />
<br />
<syntaxhighlight lang=bash>% sudo docker run --rm -p 8080:8080 helloheaders:fpc</syntaxhighlight><br />
<br />
Visit <syntaxhighlight lang=text inline>http://127.0.0.1:8080/</syntaxhighlight> with your web browser.<br />
<br />
== See Also ==<br />
<br />
* [[Qemu and other emulators]]<br />
* [[Small Virtual Machines]]<br />
<br />
[[Category:Emulators]]<br />
[[Category:Docker]]</div>Skalogryz