PureBasic
Last update: Feb 2019, PB 5.62
This page has been visited 47713 times.
Did you ever hear of Turbo Pascal 3.0 ?
While Microsoft was producing its sophisticated but bulky compilers, in 1986 Borland introduced Turbo Pascal 3.0.
It was written in assembly, it was blazing fast and in less than 40 KB you had a compiler, an editor, a linker and a debugger all loaded in RAM at the same time.
It was something that really impressed me, computers were not as powerful as today and TP3.0 was a joy to use: everything was immediate and the programming cycle [write code -> compile -> test -> repeat] was so effortless it was like using an interpreter magically running at the speed of compiled code.
In 2004 I stumbled on PureBasic after using many generations of Visual Studio (Visual Basic and Visual C/C++) and I was hooked by experiencing again, after all those years, a sensation similar to the one TP3.0 gave me in 1986.
Small, quick, simple.
I was happy ! Unfortunately things didn't evolve as I was hoping for, but more about this later.
PureBasic is a language, you guessed it, sharing some common ground with a very large family of BASIC dialects.
It's available for Windows, Linux and Mac OSX, with its most polished implementation running under Windows.
It's possible to write cross platform programs by just using PB statements and libraries, if you limit yourself to only the common set of commands and libraries available for all the OSes.
But more realistically you'll need to write some platform specific code here and there, using the target OS APIs.
On OSX this requires some acrobatics since Cocoa it's built with Objective C in mind.
The PureBasic's IDE is one good example of cross platform application and it's written in PureBasic. It's not really identical on all the platforms but it's reasonably close, so you get an idea of what can be done.
As any language has its strong and weak points, and most of them are subjective since not everyone need (or like) the same level of power or complexity so what I find important or irrelevant can be very different from what you think.
But beyond my opinions, there is a lot of good information in here, and if you just discovered PureBasic this page can point you in various directions you can explore further and most importantly a lot sooner.
Unless explicitly stated otherwise these notes are relative to the Windows version, both x86 and x64, since that's the OS I use most.
Let's start.
PureBasic's best features:
- Executables are very small for today's standards and you don't need to ship a runtime library with your program.
- Writing a simple GUI with its gadgets is really easy, and all the gadgets maps to native controls on the various OSes, so they look right.
- It can create a single EXE, with extremely reduced dependencies (usually only the DLLs shipped with the OS unless you use the 3D commands requiring the supplied OGRE DLL).
- It can be easily used to create portable programs, self-contained in their home directory, not requiring an installer or tampering with the registry.
- Generated code is reasonably fast, roughly comparable to the code generated by old C compilers but it does not offer real optimizations.
- It uses the well known FASM to compile the assembly generated by the compiler (YASM on OSX), and it does support inline assembly mixed with PB code.
- The vast majority of Windows API functions are supported as native commands (you can always import the missing ones similarly as you do in other languages).
- Compilation time is blazing fast (it's a single pass compiler and its syntax is very simple, so this helps).
- The editor is pretty good (based on Scintilla), the debugger is very good.
- In debug mode is possible to examine the contents of memory buffers and the state of windows, files, threads, gadgets, even visualize image objects with their alpha channel if present.
- The whole package (compiler + libs + IDE) it's compact (around 100 MB) and self contained. You install/configure it in minutes and you can go in and out from it in seconds.
- It does support projects with multiple target builds (a project is a container for the many related source files you need to manage when writing bigger programs).
- It comes with a kind of memory debugger, called Purifier. Extremely useful to spot problems with pointers, buffer overruns and the like.
- If has a very interesting GUI object called CanvasGadget useful to build custom graphical controls on it.
This is especially nice to build cross-platform gadgets looking the same on different OSes and works well with the 2D drawing library, which also supports useful alpha channel operations.
- The compiler can generate code for 32 and 64 bit, and the language makes very easy to write a single source targeting both the architectures.
You need to install two instances of PureBasic though, one for 32 and one for 64 bit.
- It supports ASCII, Unicode/UTF8, multithreading all natively with specific keywords/libraries.
- You can launch different versions of the compiler (i.e. 4.50 x86, 5.00 x86, 5.10 x64 and x86, etc.) from a single instance of the IDE.
- The language is somewhat an hybrid of BASIC and C, and it's easy for a beginner but not limited to the very old "BASIC" stuff.
- You can use macros to expand code at compilation time (even if PB macros can become pretty ugly fast for not trivial tasks and they still have some bugs).
- Documentation is reasonably good even if not very strict and if you have a question about the language not covered in the manual you can probably find an answer in the very active forum.
- It does natively support the usual data types supported by BASIC, plus some extra data structures like linked list and maps.
- The version 5.20 brought the implementation of modules.
The actual implementation has severe limitations but anyway they may give you the ability to isolate code in a sort of black box, to reduce name clashing at least until your luck runs out, and hide internal stuff.
- It comes with a large and growing set of libraries.
Most of them are too simplistic but some are very useful and they may speed up developing time considerably.
Weaker points:
-
Slow and spotty bug fixing, the biggest let-down for me.
I don't remember a moment in time when all the known bugs (or something reasonably near that) were fixed before releasing a new version.
The number of known serious bugs it's low and they are manageable as long as you know about them, but the timeframe for fixing a bug is usually months, sometimes years, sometimes probably never and that's really inexcusable.
Some examples of compiler bugs to give you an idea:
Wrong boolean expression evaluation (first reported in 2009).
Params being wiped out (2014).
Float promotion not performed. (2014).
FPU stack overflow with recursion (2006).
Variable overwritten while evaluating an expression (2016).
Inconsistent application of banker's rounding (2010).
This is not esoteric stuff you encounter rarely in some very specific and strange code.
Seriously I don't know how someone can nonchalantly accept a compiler to have bugs of this kind or how its author can sleep at night knowing these bugs are still there after years.
We are talking about a compiler born (at least on Windows) around 23 years ago. Core bugs like these should not be there anymore.
Also I expect more of them just waiting to be discovered, something which happens at a slow rate probably because the user base is so small.
This would not be a problem if those were fixed right away or at least in a reasonable timeframe, but as you can see it's just not happening.
The philosophy of the developers seems to center around more libraries release after release instead of concentrating on fixing known bugs first and possibly enhance a little the language.
By the way, if you consider this a trivial matter then you are in good company and in agreement with the majority of PB users judging by the constant praise for PB and the developers you can read in the forum every time a new version is released.
-
You should consider what are the future prospects for the code you write in PureBasic today.
PureBasic until now has a proven track record of being maintained in the years and it has never been abandoned by its author and not many indie languages can say the same.
But PureBasic it's still not something near immortal like C or C++ and could stop being maintained tomorrow for many reasons.
Also it's the only compiler available for the language, you'll never be able to switch to another compiler bringing your code with you.
Furthermore the small user base and the small code repository means you have to write a lot by yourself, or translate code from other languages, or at least write the occasional wrapper instead of simply plug it in.
This may be not trivial work and can be considered instructive or wasteful, depending on your point of view.
-
You can't create static libraries from your PureBasic code. You can create a dynamic library (.dll), but not a static library (.lib).
You can create a static library only using C or ASM. This is officially supported and there is some terse info about it in the SDK coming with the compiler (not always kept updated in my experience).
There is a workaround to this, but has some limitations. (1)
Personally I prefer to stick to the inclusion of the full source code when needed. Compilation it's so fast it's not a problem, and I can navigate the "library" code with the debugger.
If I'll ever have the need to distribute a PB library not in source form I would certainly go with the DLL route.
-
Even if it is reasonably fast, only very basic optimizations are performed by the compiler through a peephole optimizer (see this post by Fred).
Things like constant folding for example, but not much more than that. Not even remotely comparable to the code generated by an optimizing compiler.
I consider this the least of its problems, the code generated is fine for the kind of programs I may wrote in PB but maybe it's something you want to check into.
Also it targets 486 instructions only. So no SSE instructions, or intrinsic functions for example.
Actually some SSE or SSE2 instructions (I don't remember exactly) are used but only in the x64 version for some simple stuff like float truncations or similar things.
If you want more you can write your critical sections in ASM directly inline in PureBasic. This can often outperform even the best compiler-optimized code and you can even use almost any kind of instructions this way.
-
Pointers are a little rigid compared to C ones. You can achieve roughly the same results but pointers' arithmetic it's more primitive.
For example you can't add an offset to a pointer inside an expression to access a memory location. You have to add the offset to the pointer variable and then use the altered pointer variable.
So you need make a copy of it in advance if this not what you wanted and you need to access the original value again.
For a discussion about this and other limitations see this thread in the forum.
Coming from another language all this requires some time before you will be comfortable with it.
-
Unused procedures are sometimes included in the final EXE.
The same can be said for library functions referenced inside uncalled procedures.
This is something which undermines one of the positive aspects of PureBasic, compactness.
-
Defined but unused variables are not reported at the end of the compilation.
This should be easy to add and it's very useful, but for some reason it's not there.
This poses the risk of leaving a variable or maybe even an array or something bigger inside a procedure because you modified it and forgot to remove a data structure no longer used.
-
The number of events associated to the GUI gadgets is extremely limited, just compare it to what you have in other GUI toolkits.
This makes inevitable to use OS API calls to fill the gap. In short: do not expect to be able to write a particularly feature-rich GUI using only PB commands and the built-in supported events.
-
PureBasic does not support unsigned numbers. This is a nuisance when you have to link a C library or just want to use APIs created with the C language in mind.
You can obviously store a C unsigned int (32 bit) inside a PureBasic long (32 bits) since the binary value is correctly stored and it's just a matter of interpretation if it's signed or not, but PureBasic will still consider it a signed number for example in comparisons, leading to problems.
-
It's a procedural language, not an object oriented language. No OOP programming. Run now if you find this crazy and never look back.
According to Fred it will never change.
Anyway PureBasic supports Interfaces. With those you can call COM objects, interface to DirectX or even build your own classes from scratch, if you really want and you constantly pay a lot of attention to what you are doing. (3)
-
While it's a lot better than old style BASICs, it's not the most modern BASIC-like language.
You don't have functions overloading, so you need to call basically identical functions with different names just because their params are of different types.
This for some kind of software (math stuff for example) can became tedious very fast.
You don't have any form of generics or templates, so general algorithms working on generic data types are mostly impossible to write.
You can't initialize an array the moment you are defining it. Another thing I believe should be pretty easy to implement, very handy, and yet missing.
As a consequence of the above, you can't even initialize an array with some static data, since you have to manually load each array's item at runtime.
This can have a performance impact when you have a function where you need a local lookup table for example, something you could build with a static initialized array.
You need to reload the array every time. There are workarounds (datasections with pointers pointing there and similar stuff), but they are just that.
They are ugly, or limited, or extremely inefficient, and often they are all the above at the same time.
It feels like being trapped in time in the late '70s on a island with no contact with the contemporary world, at least if you know other languages where this stuff is commonly available.
Putting together all I saw in my time with PB I believe the compiler is simply hard to enhance and prone to break if touched too much.
That would explain why some long standing core bugs have never been fixed and at this point probably they never will. They don't know how to do it.
For the same reason I believe language enhancements of some importance are very, very, very unlikely.
This unless something dramatic happens, like delegating the actual code generation to something else or the compiler is completely rewritten.
-
The ability to build an ASCII executable has been dropped in PB 5.50.
You can still process ASCII data, read ASCII data converted on the fly to Unicode and create ASCII and UTF8 buffers from Unicode strings, but the string model used by the compiler will be Unicode only.
This means fatter strings and slower strings processing compared to the ASCII only build we had available until now, and a larger exe even when you do not require Unicode at all.
In some cases ASCII is still all you need, and I would have appreciate to retain the option.
You can read the opinions in favor and against this in this thread to pass some time.
You can read my rants about this here, here, here and here.
You get the idea.
-
Modules were a nice surprise or so I thought initially, and I believe (as it was suggested to me by another user of the forum) we got them because the assembler used (FASM) made relatively easy to do it, since it offered a way to implement a kind of namespace separation by means of the dot-based locals mechanism, without the need of modifying the generated code too much, even if this still required some updates to the debugger and IDE.
They are comfortable enough when used with small sources, but as soon the lines count goes up and you need to split a module source in multiple files, it all become very clumsy.
All the procedures must be defined inline inside a module, so it's not possible to declare them in the body of the module and define them elsewhere with a module's name qualifier as part of their name (as C++ does with classes for example).
You can't even repeat the Module / EndModule pair multiple times and have the compiler merge the different instances into one.
What you can do is just use include files inside Module / EndModule to split the code in different files.
Of course when you look at any of those files, you don't have any idea if that is normal 'global' code or code part of a module, since as we said above they are inline and there is no name qualifier.
But these are just small complaints, there are bigger problems.
The idea behind PB modules seems to be roughly inspired to the namespace concept used in other languages, but:
-
You can't access objects in the global namespace from a module.
This just makes your life miserable without reason when you need to access a global object from inside a module, for example a constant (maybe to configure a module), or a globally imported function from a library you want to use in the module AND in your main program too (think for example to OpenGL functions).
There are possible workarounds, like moving global objects inside a common module (the officially suggested solution), or passing a pointer to a global object through an initialization function in the module.
The same problem has been already solved in the best way many times before in other languages through a scope resolution operator, something like "::" or "global::", and they should have just copied it.
Since you can do something similar anyway by using a common module, a pointer, or even UseModule to import a specific module inside the scope of another one or inside the main scope, why don't just implement the "::" and let me do what I want easily without these unnecessary steps ?
Also note how residents are immune to this problem and can be magically accessed from inside a module, something I find inconsistent.
Moreover the idea of the module as a black box, unless we are talking about trivial examples, it's a myth which cannot be attained.
Even using the officially supported common module mechanism mentioned above, the importing module would obviously depend on the common one.
Not only that, the importing module must also know the name of the common module it wants to import, whilst by using "::" it doesn't need to know anything.
It can just assume what it needs it's there in the global scope like any other PB command and use it by prepending a "::" to the symbol name.
Even when you use some OS APIs in your module you depend on them and if the OS version change or an API is not present anymore your module is broken because an external dependency is missing.
So instead of trying to enforce some philosophical and unreal view I would prefer to have something flexible to use in real life and to be able to choose what's best on a case by case basis.
This has been requested in the forum by some people (myself included) when modules were introduced but with no results.
-
It's not allowed to define a procedure inside a module with the same name of a PB command.
A better way would have been to give precedence to what declared in a module, and make still accessible the overridden PB command from the global namespace by using "::".
This is a problem if you want to call a function in your module like a PB command (for example LoadImage()) and more importantly is a problem if in a future PB version a name collision happens anyway
because a new PB command has the same name of a function you defined in the module.
Also, if your function was not a private but a public one, this will cause your interface to break.
If what defined in your module had priority over the new conflicting command, your public interface would continue to work exactly the same, and you could access the new PB command from inside the module by using "::" if required.
PB modules are an half baked answer to a problem they don't solve properly.
This has been a missed opportunity to add something really useful to the language in a proper way.
... and some other points:
-
The official visual designer is a moving target. Once was a separated project, for a while was practically abandoned, and now there is a new version trying to be more tightly integrated with the IDE.
I don't have a definite opinion on that because I do all my simple GUIs by hand, but if it's important to you, you better give it a whirl and see for yourself. (2)
-
The support for accelerated 2D graphics (DirectX 11 under Windows) it's simple but effective (sprites, 2D drawing, mouse and keyboard libraries) but I tested it superficially simply because I like OpenGL and part of the fun resides in writing my own library for that.
-
There is a support for 3D graphics through an adaptation of the OGRE engine to make it usable through a flat API. It's simplified compared to using OGRE directly but it's moving forward with frequent additions.
Using it requires the distribution of a customized OGRE DLL with your program. Again I don't really use it so you better ask about it to someone else in the forum.
-
Being a language still developed a new compiler version can break your old code.
In my experience the changes you have to make to your programs are really minor, I never spent more than a couple of hours to update a program to a newer compiler version.
Wrapping it up
I think PB can have its place, it's quite unique and I don't know of anything else directly comparable.
It can be a handy tool if you are an hobbyist or a nostalgic BASIC programmer, or if you simply like BASIC languages and you want a small, fast, compact environment to code in without distractions or layers of added complexity, if you just dislike OOP,
or if you normally use more complex languages and you are looking for a tool to quickly write support utilities, etc.
Some aspects of it are very nice and at the same time some of its shortcomings are extremely annoying to live with.
In the forum some people are asking for years for some useful extensions to the language or a specific bug to be fixed and they are growing older waiting for it.
Don't expect the core language to evolve, I finally gave up on that and I no longer waste my time in the forum discussing what could be done and how.
If you have certain expectations you should move on to something else, even at the cost of an initially steeper learning curve.
If you are happy with its current state, you can enjoy the positives I listed before.
Now it's time for you to try it and make your mind about it.
Read the subsections of the forum dedicated to reporting bugs, look at the kind of reported bugs, how old they are, the replies or lack thereof, and read the manual once from top to bottom (very few do it, and it shows in the forum).
Most of all, try the whole package for a while, create a small but real project with it and consider the pros and cons of your experience.
(1) See TailBite by ABBKlaus.
(2) See also PureForm a visual designer by Gnozal (probably no longer supported), PureVision (shareware).
(3) See OOP tutorial by srod, Class template by luis.
Some links
PureBasic : The official home page of the language.
PureBasic forum : The official english forum.
PureBasic Team Blog : The official blog maintained by the PureBasic's developers.
Interview with Frédéric 'AlphaSND' Laboureur (fred) (2005) : The creator of PureBasic.
Interview with Timo Harter (freak) (2009) : The other PureBasic's developer.
Interview with Frédéric 'AlphaSND' Laboureur (fred) (2012) : A more recent interview with Fred.
PureArea (André Beer) : Home of CodeArchiv.
PureBasic OpenSource Libraries : A collection of open-source libraries usable through the LGPL license, mostly outdated but they can give you some ideas nevertheless.
PureBasic Survival Guide (blueznl) : A great guide you can use to complement to the official PureBasic's documentation while learning the language.
Once upon a time you could have sent me an e-mail by clicking somewhere around here ....