The contents in this document are available in:
© 2020-2023 UnionTech Software Technology Co., Ltd.
This is the repository of the programming language named Unilang, including documents and an interpreter as the reference implementation.
See the sections below to build and use the interpreter.
Unilang is a general purpose programming language project proposed to adapt to more effective and flexible development of desktop environment applications.
Currently, there are many options for desktop application development, with their respective advantages and disadvantages:
From the perspective of higher-level structure, different types of GUI solutions also have different technical limitations in the sense of architecture, which greatly limits the choices of real general solutions:
WS_EX_LAYERED is only supported in the top-level windows but not in the child windows before Windows 8.HWND), such as WPF.So, no existing solution can take into account all kinds of problems and become the preferred solution for desktop development without doubt.
A considerable part of the above problems (performance, deployment difficulty and portability) are directly related to the language. Looking at the language part, we find that existing languages are not able to solve all these major issues well enough, because:
We are eager to have a new language to solve all the above pain points. However, it is not enough to provide a new language design and implementation. New languages are not magic to automatically solve legacy problems -- especially considering that there is no lack of "new" programming languages in the market, but they still do not meet the needs.
One of the technical reasons for this situation is that many designs are too focused on specific requirements and lack of consideration of the general factors of long-term evolution of the language, and their applicability outside the expected target areas drops sharply, which is not universal enough; or they fail to balance universality and complexity. This makes the application field slightly deviate from the expectation and exposes the limitations of the original design. Even if users knows how to improve a language, they will encounter practical difficulties in development of the language and eventually give up.
If new options are put forward without avoiding this situation, it will only further hinder the solution of the problem. Therefore, on the basis of meeting the needs, we hope that the new language can truly achieve universality in a deeper way - by reducing the built-in ad-hoc features specifically for individual problem domains and replacing them with a more general set of primitive features.
Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.
Well ... but why a new language?
As we have known, most properties of a programming language are provided through the features specificed by the language rules. Moreover, some other required properties are guaranteed by deliberately lacking of specific set of features (i.e. misfeatures).
Users want features but not misfeatures. In reality, the concrete sets of features and misfeatures needed by users are not totally fixed. It might be easy to add new features to a current programming language. However, it is almost always quite different (if not impossible) to remove the misfeatures due to the risks on (in)compatibilities, unless the language is expected to do so, by design.
There are many technical concerns to determine whether a feature satisfies our need here, and the decisions may likely vary for other uses. So, the common features set would be surprisingly small. But almost all industrial languages just provide too much, and those features are not easy to be added back on demand, by users (rather than the designers) of the language. In such case, it just means many misfeatures are unavoidable.
For practical reasons, we decide to not change the current languages (which is likely to add additional complexity on the languages and to create more misfeatures to some users), but to reinvent our own wheel, to solve the problem.
We also prefer only one language to serve the needs as possible. Otherwise, we can have several languages. Such languages naturally serve to different problem domains, hence domain-specific languages (DSLs).
The latter might work well when such domains share few common works. On the other hand, to our experience, it is a pain to maintain different DSLs together for a slightly greater problem domain consisting of related subdomains. There are also unneeded cost on interoperability. Even we want different tool sets specialized for different problem domains, there should been more common design and implementation reused among different languages. They should not be totally independent by design. In other words, the replacements of the DSLs should better be dialects of a general-purpose language.
Some users may have concerns on the look and feel of the resulted "one" language. It is true that a unique and universal syntax can hardly work across different problem domains, because there are conflict needs on the syntax: we may need radically different visual styles for different purpose. But there is actually no law to rules out the possibility to support more than one sets of concrete syntaxes in one language.
So, the syntaxes of a language should not be the problem. If the syntax is not satisfying, just change it. This is done by users, but not the designers of the languages. Of course there is some cost, but it should be cheaper than designing and implementing a radically different language, once the underlying language is properly provided.
As said, to design a language indeed general-purpose, we don't want to pile features at the top of the existing ones.
Instead of specifying a rich feature set, we first provide the base language, which only have the features fundamental enough. Such features can be used to implement other features in terms of library API. The language is then derived.
The base language is designed specifically for the ease of integration in the very beginning. In other words, besides the common feature set, it also serves the problem domain of language derivation, which is neglected by most programming languages. It should be relatively easy to use in this aspect compare to most (if not all) mainstream programming languages. To be concrete, the basic languages works well without many popular core language features (like type systems) in other languages. This significantly decreases the risks of misfeatures from premature designs for all derivations.
We acknowledge that users may have the need of out-of-box features not provided in the native language design. This is solved by allowing fundamental features of the language more programmable, i.e. most features are in the libraries.
We also encourage users to add new sets of features from the current language and contribute to us in the form of libarary.
This approach, like less contention with locks of smaller granularity, also improves the potential parallelism on the process of the language evolation. We have identified the high cost of communication in the development process in some major program languages and try to prevent some foreseeable problems in this way. Hopefully this would be more efficient than alternative methods.
NOTE The contents in this section can be regarded as the rationale of above 2 sections.
NOTE This section may involve less well-known theoretical viewpoints. Although the background knownledge is verifiable, relevant references are intentionally not provided here; this is reserved as an exercise for readers interested in language design.
The designer of this language believes that:
constexpr in C++ has to be written differently and cannot be merged?GUI is a major problem domain with non-trivial programming works in reality. It is important and complicated enough. In particular, it has quite a lot different subdomains of problems, and many solutions already choose the mixture of different DSLs. Thus, it is a great field to experiment our methodology.
Due to the intrinsic complexity of the problems, we don't expect an omnipotent solution to meet all requirements. Instead, we have different plans to improve the current status.
Among all the plans, the dynamic nature of the languages plays an important role. We believe it better to be dynamic for features like widget layout, widget hierarchy, runtime object inspection and hot reloading. Any effort to implement them in a static language needs to implement a dynamic description of the program at first, essentially implementing a dynamic language if it is reusable. Avoiding the need to choose a different solution to work around the problems here is an improvment over many technologies.
There are more different improvements even when compared to current solutions already using some dynamic language:
(More concrete works TBD here.)
Unilang is the language part of the new solution to comprehensively solve the existing problems. The distinguishing features are:
unsafe keyword to mark "unsafe" code fragments. The most primitive features are "unsafe" by default.const type qualifier in C++, objects referenced by lvalues are allowed to be marked as immutable (read-only), instead of the default convention of immutable values as in languages like Rust.unsafe, ususally the safety guarantees defined by the language rules are dropped altogether, and a part of these guarantees cannot be retained. Even if this problem is ignored, these languages lack mechanisms to allow users to provide stricter guarantees integrating into current ones.
const as currently mandated by ISO C++, because the immutablity of the key is exactly defined by the equivalent relationship derived from the comparison object. But the type system of C++ cannot distinguish these two case of immutablilty, leading to over-specification in the types of API, which blocks some operations on the key.
const_cast to cease the type safety guarantee introduced by const totally and assuming it not destroyed by other operations is a helpless workaround here (the "more difficult" situation; the type safety cannot be restored, and the effect is worse).const (the "more difficult" situation).To keep universality, Unilang does not provide GUI functionailty as built-in features, but provides related APIs through libraries. In the current plan, Unilang will support Qt-based binding libraries to ease the transition of some existing desktop application projects. The language design of Unilang keeps sufficient abstract ability and extensibility, allowing direct implementation of GUI frameworks in the future.
The following documents are maintained in this project:
README: This document. It introduces the overall status, usage and main supported features.
The contributors of this project shall generally be able to determine the relevance of the contents in the above documents and the corresponding implemented modifications (if any).
If there is inconsistency between the contents of the document or it does not match other parts of the project, please contact the maintainers to report the defect.
The documents shall provide no descriptions with misconceptions on the objects being documented.
The documents should usually be consistent with other parts of the repository (including the interpreter and the standrd library code which is used as the reference implementation).
However, as this project is in an early development stage, some documents may have differences on the requirements of consistency.
master branch) in a limited way.README document.If not all requirements above are met, such inconsistent shall be treated as the defects of the documents or the objects being described in the documents.
This project supports several ways to build.
The supported hosted environments are MSYS2 MinGW32 and Linux.
The following instructions using the root directory of the repository as the current working directory.
Some external dependencies are used in the source form, provided as the git submodules.
The building environment relies on the following tools:
git
bash
C++ toolchain supporting std=c++11, any of:
libffi
pkg-config
Qt 5
The following dependencies are optional:
LLVM 7
llvm-configLLD
The following commands illustrates how to prepare the build environment by package managers:
# Some dependencies may have been preinstalled.
# MSYS2 (no LLVM 7)
pacman -S --needed bash coreutils git mingw-w64-x86_64-{gcc,binutils,libffi,lld,pkgconf,qt5-base,qt5-declarative} parallel
# Arch Linux
sudo pacman -S --needed bash coreutils git gcc binutils libffi pkgconf qt5-base qt5-declarative parallel
yay -S llvm70 # Or some other AUR frontend command.
# Debian (buster/bullseye)/Ubuntu (bionic-updates/focal)/Deepin
sudo apt install bash coreutils git g++ libffi-dev llvm-7-dev pkg-config qtbase5-dev qtdeclarative5-dev parallel
LLVM is optional. Use non-empty environment variable UNILANG_NO_LLVM to avoid using of LLVM in building the interpreter. LLVM-based JIT is then disabled.
NOTE There may be different version of LLVM provided by the system, which would clash with LLVM 7 on loading. Currently mixing different version of LLVM is not supported. Thus, UNILANG_NO_LLVM is needed in such case.
If there is no LLVM 7 provided by the system, you may need to build it by yourself. In this project, the environment variable USE_LLVM_PREFIX specifies the custom path prefix of the LLVM installation, and it will be used by the scripts when needed.
There are several caveats for MSYS2 x86_64 toolchain:
-fuse-ld=lld in the linker command line).-fuse-ld=bfd) and LLD.
See also the environment configuration below for some optional additional dependencies.
QT_NAMESPACE is not supported.pkg-config, for following packages:
Qt5WidgetsQt5QuickBefore the build, run the following command to ensure the external source dependencies are setup properly:
git submodule update --init
If there exists the submodule update, and the script install-sbuild.sh is already executed previously, it is necessary to cleanup the intermediate file to make sure the script work properly again, by the command:
rm -f 3rdparty/.patched
Alternatively, the following git command can make the cleanup:
git clean -f -X 3rdparty
Run the script build.sh to build directly and the output executable file will be put into the current working directory:
./build.sh
This uses g++ by default. The environment variable CXX can override the default, as:
env CXX=clang++ ./build.sh
The default compiler options are -std=c++11 -Wall -Wextra -O3 -DNDEBUG if the name of the output directory (see below) does not contain debug, or -std=c++11 -Wall -Wextra -g otherwise. Similarly, use the environment variable CXXFLAGS can override the default value.
The script supports LDFLAGS to specify extra linker options (like -fuse-ld=lld). The default value is empty.
The script uses shell command lines to call the compiler driver specified by $CXX directly. By default, the script will test whether GNU parallel is available. If so, parallel command is used to build the interpreter in parallel.
The following optional environment variables are used in the script to specify the location of output files:
Unilang_Output: The output path of the interpreter executable file. The default value is unilang.Unilang_BuildDir: The directory for intermediate files during the build. The default value is the directory of the value of Unilang_Output.This script also supports setting the following non-empty environment variables to tweak the behavior:
NoParallel: Skip test for the parallel command and disable the parallel build.Verbose: Enable the verbose output.The advantage of this script is the ease to use without further configurations. It may be suitable for one-time testing and deployment.
With the script of external build tools (zh-CN), more configurations are supported. This method is more suitable for the development in this project.
Currently, only x86_64 architecture is supported on Linux.
The following instructions assume the number of maximum parallel build tasks as $(nproc). This can be overridden by a positive integer in the command lines.
The configuration of the environment installs the tools and build dependencies, which requires to be run only once. (But it is recommended to configure again after any git submodules update.)
The installed files are built from the source code in 3rdparty/YSLib.
For Linux targets, first it is required to keep the external dependencies specific for the installation, even they are not used in this project at all:
For example, by package managers:
# Arch Linux
sudo pacman -S freetype2 --needed
# Debian/Ubuntu/Deepin
sudo apt install libfreetype6-dev
For automatic update of binary dependencies and patching the source in the script below, the following dependencies are needed:
wget7zased (avoiding Win32 ones which may corrupt the line ending)The following dependency can accelerate the installation in ./install-sbuild.sh:
parallelFor example, by package managers:
# MSYS2
# XXX: Do not use mingw-w64-x86_64-sed to ensure the EOL characters as-is.
pacman -S --needed mingw-w64-x86_64-wget p7zip sed parallel
# Arch Linux
sudo pacman -S --needed wget p7zip sed parallel
yay -S llvm70 # Or some other AUR frontend command.
# Debian (buster/bullseye)/Ubuntu (bionic-updates/focal)/Deepin
sudo apt install wget p7zip-full sed parallel
Then run the script ./install-sbuild.sh to install the external tools and libraries. The script updates precompiled binary dependencies, then builds and deploys the tools and the libraries. The binaray dependencies are deployed directly into the source tree. Currently the prebuilt dependencies only supports the x86_64-linux-gnu target. Any output files built by this project do not need to deploy these binary dependencies.
NOTE The binary dependencies installed by the script may change as per the build environment updates. Nevertheless, currently it is guaranteed no binary-incompatible parts are depended on. Therefore, it is optional to update the binary dependencies. However, after the update of the build environment, usually the script needs to run again, to ensure up-to-date tools and libraries are installed. If the binary dependencies are no longer existing, the script will automatically fetch them from the network.
The following environment variables controls the behavior of the script:
SHBuild_BuildOpt: The build options, defaulted to -xj,$(nproc), where $(nproc) is the number of parallel builds.SHBuild_SysRoot: The root directory for the installation, defaulted to "3rdparty/YSLib/sysroot".SHBuild_BuildDir: The directory for intermediate files during the installation, defaulted to "3rdparty/YSLib/build".SHBuild_Rebuild_S1: If not empty, specify the rebuild of stage 1 SHBuild (zh-CN) (it might be slow).
3rdparty/YSLib/Tools/Scripts requires the rebuild to prevent imcompatibility.parallel command.Using of the installed binary tools and dynamic libraries requires the configurations of paths, as:
# Configure PATH.
export PATH=$(realpath "$SHBuild_SysRoot/usr/bin"):$PATH
# Configure LD_LIBRARY_PATH (reqiured for Linux with non-default search path).
export LD_LIBRARY_PATH=$(realpath "$SHBuild_SysRoot/usr/lib"):$LD_LIBRARY_PATH
The export commands can be put into the initialization scripts of the shell (such as .bash_profile) so there are no need to repeat.
After the configuration of the build environment, run the script sbuild.sh to build this project.
This method support parallel builds and different configurations compared to the direct building script, as:
./sbuild.sh release-static -xj,$(nproc)
The command above specify the output built files in the directory build/.release-static. To avoid the confilicts with intermediate files, the output executable files always having the suffix .exe.
Here, release-static is the configuration name.
Let non-empty configuration name $CONF, when $SHBuild_BuildDir is not empty, the output directory is SHBuild_BuildDir/.$CONF; otherwise, the output directory is build/.$CONF .
When $CONF has the prefix by debug, the debug versions of the libraries (already built from 3rdparty source in the previously installation steps for the build environment configuration) are used automatically, otherwise libraries of non-debug version are used. When $CONF has the suffix static, static libraries are used instead of dynamic libraries. The use of dynamic libraries makes the output executable file relying on the files in $LD_LIBRARY_PATH set up previously.
Running the direct building script links against static libraries. This is roughly equivalent to the non-debug static library builds here.
The interpreter executable file using of the dynamic library configuration relies on the correspoinding dynamic library files at runtime. It is necessary to ensure these libraries files can be found by the system (which should be prepared by the steps of the above build environment configuration), as:
# MinGW32
export PATH=$(realpath "$SHBuild_SysRoot/usr/bin"):$PATH
# Linux
export LD_LIBRARY_PATH=$(realpath "$SHBuild_SysRoot/usr/lib"):$LD_LIBRARY_PATH
If LLVM is installed to non-default location by means other than the system package manager, it may need also to configure for LLVM, as:
# Linux
export LD_LIBRARY_PATH=/opt/llvm70/lib:$LD_LIBRARY_PATH
The LD_LIBRARY_PATH in Linux configurations above can also be configured by other ways instead, such as ldconfig.
Use of static library instead can avoid the necessity of the configurations above. However, LLVM may be deployed only with dynamic libraries.
CAUTION There is no guarantee to ensure the compatibility among external binary dependencies not configurated by the scripts. They may need other reliable ways of deployment, e.g. by the system package manager. Relying on such libraries causes the final executable of the interpreter not portable among different system enviornments (like different Linux distrobutions).
Running the executable file of the interpreter enters the REPL, and the interpreter run in the interactive mode. Alternatively, specify a script name in the command line, then the interpreter will be run in the scripting mode, and the script will be loaded and executed. The script name - is treated as the standard input.
Running the interpreter with the command line option -e accepts string arguments to specify the code being evaluated before entering the interactive or scripting mode. The option -e can occur multiple times with one string argument for each instance. These strings are treated as Unilang source code and to be evaluated in order.
The command line of interpreter also supports the POSIX convention of --, which indicates all command line arguments after are not interpreted as command line options. This allows to specify script names same to options.
The commond line option -h or --help shows the help message of the interpreter.
Optionally, the environment variables are handled by the interpreter:
ECHO: If not empty, enable REPL echo. This makes sure the interpreter prints the evaluated result after each interaction session.UNILANG_NO_JIT: Disable JIT compilation, using pure interpreter instead.UNILANG_NO_SRCINFO: Disable source information for diagnostic message output. The source names are still used in the diagnostics.UNILANG_PATH: Specify the library load path. See the descriptions of standard library load in the [language specifciation (zh-CN)], as well as the descriptions of standard library operations in the implementation document of the interpreter (zh-CN).Except the option -e, with the external echo command, the interpreter can support non-interactive input, such as:
echo 'display "Hello world."; () newline' | ./unilang
A Unilang prograrm using QtWidgets is provided.
./unilang demo/qt.txt
See demo/qt.py for the Python implementation with the equivalent functionality.
There is another demo using QtQuick as a minimalized version like the Qt official qmlscene tool:
./unilang demo/qml.txt
This demo loads the source demo/hello.qml relative to the current working directory. Stay the root directory of the repository should be OK.
./unilang demo/quicksort.txt
The file test.sh is the test script. This can be directly with a few test cases. The script will call the interpreter internally.
The test cases are specified in the script code, including call the interpreter to run the test program test.txt. In REPL, load "test.txt" also call to load the test program.
The script supports following environment variables:
UNILANG: Specify the path of the interpreter, defalted to ./unilang.PTC: If not empty, the PTC test case is run. This requires manually termination of the process. During the running, a correct PTC implementation ensures the memory footprint of the intpreter eventually not grow over time.The built executables built usingsbuild.sh are not in the current working directory. This can be called by the test script as the following command:
env UNILANG=build/.debug/unilang.exe ./test.sh
The file valgrind.sh calls the interpreter via valgrind for testing purpose.
The script uses Callgrind to collect performance data by default. The data can be viewed using kcachegrind.
Use environment variables to specify the command used to invoke the interpreter and to control the options, like:
env UNILANG=build/.release-l/unilang.exe OUTPUT=build/.release-l/callgrind.out LOGOPT=--log-file=build/.release-l/callgrind.log ./valgrind.sh -e ''
Variables may have default values if not explicitly specified. The command above is actually eqivalent to the direct call to ./test-valgrind.sh -e '' without specifying any environment variables explicitly.
See the source code of the script for other supported variables and the default values of the variables.
See the examples introduction of Unilang (zh-CN) (not all of them are yet supported) the feature list document (zh-CN) for features.
See also the language specification (zh-CN) and the design and the implementation document of the interpreter (zh-CN).
Output of imprecise numbers are compatible to the format of C++ functions in <cstdio>, and it is not guaranteed to have expected lexical notations when non-default locales are used:
..Currently, the output number under non-default locales may not be successfully parsed as the numerical literals.
We encourage reporting issues and contributing modifications to the project.
See Contribution Guidelines for Developers for basic information.
However, the following probably different rules are more applicable in this project:
[optinal scope: ]<description>.
: is optional and it appertain to the scope. should be used for alignment.
u8 literals, so some code may have different meaning and it shall not be used here.-std=c++11 -pedantic-errors.{ and } shall be in one line by itself, except those consist a braced-init-list or the outermost block of a lambda-expression.
INC_Unilang_ is reserved to the header file guards.PascalCase.namespace directives shall not be used (using namespace are only allowed for internal implementations).typedef shall be used.
using instead.\ shall be used instead of @.* or / shall be used together.
//! rather than ///.en-US locale.en-US.. in the filename, before the prefixed . and file extension (if any).- and ISO 3166-1 code.en-US edition omits .en-US. For instance, README.md is the filename of the translated edition in en-US locale. The corresponding edition in zh-CN is README.zh-CN.md.Unilang is released under BSD-2-Clause-Patent.
The contents in this document are available in:
© 2020-2023 UnionTech Software Technology Co., Ltd.
This is the repository of the programming language named Unilang, including documents and an interpreter as the reference implementation.
See the sections below to build and use the interpreter.
Unilang is a general purpose programming language project proposed to adapt to more effective and flexible development of desktop environment applications.
Currently, there are many options for desktop application development, with their respective advantages and disadvantages:
From the perspective of higher-level structure, different types of GUI solutions also have different technical limitations in the sense of architecture, which greatly limits the choices of real general solutions:
WS_EX_LAYERED is only supported in the top-level windows but not in the child windows before Windows 8.HWND), such as WPF.So, no existing solution can take into account all kinds of problems and become the preferred solution for desktop development without doubt.
A considerable part of the above problems (performance, deployment difficulty and portability) are directly related to the language. Looking at the language part, we find that existing languages are not able to solve all these major issues well enough, because:
We are eager to have a new language to solve all the above pain points. However, it is not enough to provide a new language design and implementation. New languages are not magic to automatically solve legacy problems -- especially considering that there is no lack of "new" programming languages in the market, but they still do not meet the needs.
One of the technical reasons for this situation is that many designs are too focused on specific requirements and lack of consideration of the general factors of long-term evolution of the language, and their applicability outside the expected target areas drops sharply, which is not universal enough; or they fail to balance universality and complexity. This makes the application field slightly deviate from the expectation and exposes the limitations of the original design. Even if users knows how to improve a language, they will encounter practical difficulties in development of the language and eventually give up.
If new options are put forward without avoiding this situation, it will only further hinder the solution of the problem. Therefore, on the basis of meeting the needs, we hope that the new language can truly achieve universality in a deeper way - by reducing the built-in ad-hoc features specifically for individual problem domains and replacing them with a more general set of primitive features.
Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.
Well ... but why a new language?
As we have known, most properties of a programming language are provided through the features specificed by the language rules. Moreover, some other required properties are guaranteed by deliberately lacking of specific set of features (i.e. misfeatures).
Users want features but not misfeatures. In reality, the concrete sets of features and misfeatures needed by users are not totally fixed. It might be easy to add new features to a current programming language. However, it is almost always quite different (if not impossible) to remove the misfeatures due to the risks on (in)compatibilities, unless the language is expected to do so, by design.
There are many technical concerns to determine whether a feature satisfies our need here, and the decisions may likely vary for other uses. So, the common features set would be surprisingly small. But almost all industrial languages just provide too much, and those features are not easy to be added back on demand, by users (rather than the designers) of the language. In such case, it just means many misfeatures are unavoidable.
For practical reasons, we decide to not change the current languages (which is likely to add additional complexity on the languages and to create more misfeatures to some users), but to reinvent our own wheel, to solve the problem.
We also prefer only one language to serve the needs as possible. Otherwise, we can have several languages. Such languages naturally serve to different problem domains, hence domain-specific languages (DSLs).
The latter might work well when such domains share few common works. On the other hand, to our experience, it is a pain to maintain different DSLs together for a slightly greater problem domain consisting of related subdomains. There are also unneeded cost on interoperability. Even we want different tool sets specialized for different problem domains, there should been more common design and implementation reused among different languages. They should not be totally independent by design. In other words, the replacements of the DSLs should better be dialects of a general-purpose language.
Some users may have concerns on the look and feel of the resulted "one" language. It is true that a unique and universal syntax can hardly work across different problem domains, because there are conflict needs on the syntax: we may need radically different visual styles for different purpose. But there is actually no law to rules out the possibility to support more than one sets of concrete syntaxes in one language.
So, the syntaxes of a language should not be the problem. If the syntax is not satisfying, just change it. This is done by users, but not the designers of the languages. Of course there is some cost, but it should be cheaper than designing and implementing a radically different language, once the underlying language is properly provided.
As said, to design a language indeed general-purpose, we don't want to pile features at the top of the existing ones.
Instead of specifying a rich feature set, we first provide the base language, which only have the features fundamental enough. Such features can be used to implement other features in terms of library API. The language is then derived.
The base language is designed specifically for the ease of integration in the very beginning. In other words, besides the common feature set, it also serves the problem domain of language derivation, which is neglected by most programming languages. It should be relatively easy to use in this aspect compare to most (if not all) mainstream programming languages. To be concrete, the basic languages works well without many popular core language features (like type systems) in other languages. This significantly decreases the risks of misfeatures from premature designs for all derivations.
We acknowledge that users may have the need of out-of-box features not provided in the native language design. This is solved by allowing fundamental features of the language more programmable, i.e. most features are in the libraries.
We also encourage users to add new sets of features from the current language and contribute to us in the form of libarary.
This approach, like less contention with locks of smaller granularity, also improves the potential parallelism on the process of the language evolation. We have identified the high cost of communication in the development process in some major program languages and try to prevent some foreseeable problems in this way. Hopefully this would be more efficient than alternative methods.
NOTE The contents in this section can be regarded as the rationale of above 2 sections.
NOTE This section may involve less well-known theoretical viewpoints. Although the background knownledge is verifiable, relevant references are intentionally not provided here; this is reserved as an exercise for readers interested in language design.
The designer of this language believes that:
constexpr in C++ has to be written differently and cannot be merged?GUI is a major problem domain with non-trivial programming works in reality. It is important and complicated enough. In particular, it has quite a lot different subdomains of problems, and many solutions already choose the mixture of different DSLs. Thus, it is a great field to experiment our methodology.
Due to the intrinsic complexity of the problems, we don't expect an omnipotent solution to meet all requirements. Instead, we have different plans to improve the current status.
Among all the plans, the dynamic nature of the languages plays an important role. We believe it better to be dynamic for features like widget layout, widget hierarchy, runtime object inspection and hot reloading. Any effort to implement them in a static language needs to implement a dynamic description of the program at first, essentially implementing a dynamic language if it is reusable. Avoiding the need to choose a different solution to work around the problems here is an improvment over many technologies.
There are more different improvements even when compared to current solutions already using some dynamic language:
(More concrete works TBD here.)
Unilang is the language part of the new solution to comprehensively solve the existing problems. The distinguishing features are:
unsafe keyword to mark "unsafe" code fragments. The most primitive features are "unsafe" by default.const type qualifier in C++, objects referenced by lvalues are allowed to be marked as immutable (read-only), instead of the default convention of immutable values as in languages like Rust.unsafe, ususally the safety guarantees defined by the language rules are dropped altogether, and a part of these guarantees cannot be retained. Even if this problem is ignored, these languages lack mechanisms to allow users to provide stricter guarantees integrating into current ones.
const as currently mandated by ISO C++, because the immutablity of the key is exactly defined by the equivalent relationship derived from the comparison object. But the type system of C++ cannot distinguish these two case of immutablilty, leading to over-specification in the types of API, which blocks some operations on the key.
const_cast to cease the type safety guarantee introduced by const totally and assuming it not destroyed by other operations is a helpless workaround here (the "more difficult" situation; the type safety cannot be restored, and the effect is worse).const (the "more difficult" situation).To keep universality, Unilang does not provide GUI functionailty as built-in features, but provides related APIs through libraries. In the current plan, Unilang will support Qt-based binding libraries to ease the transition of some existing desktop application projects. The language design of Unilang keeps sufficient abstract ability and extensibility, allowing direct implementation of GUI frameworks in the future.
The following documents are maintained in this project:
README: This document. It introduces the overall status, usage and main supported features.
The contributors of this project shall generally be able to determine the relevance of the contents in the above documents and the corresponding implemented modifications (if any).
If there is inconsistency between the contents of the document or it does not match other parts of the project, please contact the maintainers to report the defect.
The documents shall provide no descriptions with misconceptions on the objects being documented.
The documents should usually be consistent with other parts of the repository (including the interpreter and the standrd library code which is used as the reference implementation).
However, as this project is in an early development stage, some documents may have differences on the requirements of consistency.
master branch) in a limited way.README document.If not all requirements above are met, such inconsistent shall be treated as the defects of the documents or the objects being described in the documents.
This project supports several ways to build.
The supported hosted environments are MSYS2 MinGW32 and Linux.
The following instructions using the root directory of the repository as the current working directory.
Some external dependencies are used in the source form, provided as the git submodules.
The building environment relies on the following tools:
git
bash
C++ toolchain supporting std=c++11, any of:
libffi
pkg-config
Qt 5
The following dependencies are optional:
LLVM 7
llvm-configLLD
The following commands illustrates how to prepare the build environment by package managers:
# Some dependencies may have been preinstalled.
# MSYS2 (no LLVM 7)
pacman -S --needed bash coreutils git mingw-w64-x86_64-{gcc,binutils,libffi,lld,pkgconf,qt5-base,qt5-declarative} parallel
# Arch Linux
sudo pacman -S --needed bash coreutils git gcc binutils libffi pkgconf qt5-base qt5-declarative parallel
yay -S llvm70 # Or some other AUR frontend command.
# Debian (buster/bullseye)/Ubuntu (bionic-updates/focal)/Deepin
sudo apt install bash coreutils git g++ libffi-dev llvm-7-dev pkg-config qtbase5-dev qtdeclarative5-dev parallel
LLVM is optional. Use non-empty environment variable UNILANG_NO_LLVM to avoid using of LLVM in building the interpreter. LLVM-based JIT is then disabled.
NOTE There may be different version of LLVM provided by the system, which would clash with LLVM 7 on loading. Currently mixing different version of LLVM is not supported. Thus, UNILANG_NO_LLVM is needed in such case.
If there is no LLVM 7 provided by the system, you may need to build it by yourself. In this project, the environment variable USE_LLVM_PREFIX specifies the custom path prefix of the LLVM installation, and it will be used by the scripts when needed.
There are several caveats for MSYS2 x86_64 toolchain:
-fuse-ld=lld in the linker command line).-fuse-ld=bfd) and LLD.
See also the environment configuration below for some optional additional dependencies.
QT_NAMESPACE is not supported.pkg-config, for following packages:
Qt5WidgetsQt5QuickBefore the build, run the following command to ensure the external source dependencies are setup properly:
git submodule update --init
If there exists the submodule update, and the script install-sbuild.sh is already executed previously, it is necessary to cleanup the intermediate file to make sure the script work properly again, by the command:
rm -f 3rdparty/.patched
Alternatively, the following git command can make the cleanup:
git clean -f -X 3rdparty
Run the script build.sh to build directly and the output executable file will be put into the current working directory:
./build.sh
This uses g++ by default. The environment variable CXX can override the default, as:
env CXX=clang++ ./build.sh
The default compiler options are -std=c++11 -Wall -Wextra -O3 -DNDEBUG if the name of the output directory (see below) does not contain debug, or -std=c++11 -Wall -Wextra -g otherwise. Similarly, use the environment variable CXXFLAGS can override the default value.
The script supports LDFLAGS to specify extra linker options (like -fuse-ld=lld). The default value is empty.
The script uses shell command lines to call the compiler driver specified by $CXX directly. By default, the script will test whether GNU parallel is available. If so, parallel command is used to build the interpreter in parallel.
The following optional environment variables are used in the script to specify the location of output files:
Unilang_Output: The output path of the interpreter executable file. The default value is unilang.Unilang_BuildDir: The directory for intermediate files during the build. The default value is the directory of the value of Unilang_Output.This script also supports setting the following non-empty environment variables to tweak the behavior:
NoParallel: Skip test for the parallel command and disable the parallel build.Verbose: Enable the verbose output.The advantage of this script is the ease to use without further configurations. It may be suitable for one-time testing and deployment.
With the script of external build tools (zh-CN), more configurations are supported. This method is more suitable for the development in this project.
Currently, only x86_64 architecture is supported on Linux.
The following instructions assume the number of maximum parallel build tasks as $(nproc). This can be overridden by a positive integer in the command lines.
The configuration of the environment installs the tools and build dependencies, which requires to be run only once. (But it is recommended to configure again after any git submodules update.)
The installed files are built from the source code in 3rdparty/YSLib.
For Linux targets, first it is required to keep the external dependencies specific for the installation, even they are not used in this project at all:
For example, by package managers:
# Arch Linux
sudo pacman -S freetype2 --needed
# Debian/Ubuntu/Deepin
sudo apt install libfreetype6-dev
For automatic update of binary dependencies and patching the source in the script below, the following dependencies are needed:
wget7zased (avoiding Win32 ones which may corrupt the line ending)The following dependency can accelerate the installation in ./install-sbuild.sh:
parallelFor example, by package managers:
# MSYS2
# XXX: Do not use mingw-w64-x86_64-sed to ensure the EOL characters as-is.
pacman -S --needed mingw-w64-x86_64-wget p7zip sed parallel
# Arch Linux
sudo pacman -S --needed wget p7zip sed parallel
yay -S llvm70 # Or some other AUR frontend command.
# Debian (buster/bullseye)/Ubuntu (bionic-updates/focal)/Deepin
sudo apt install wget p7zip-full sed parallel
Then run the script ./install-sbuild.sh to install the external tools and libraries. The script updates precompiled binary dependencies, then builds and deploys the tools and the libraries. The binaray dependencies are deployed directly into the source tree. Currently the prebuilt dependencies only supports the x86_64-linux-gnu target. Any output files built by this project do not need to deploy these binary dependencies.
NOTE The binary dependencies installed by the script may change as per the build environment updates. Nevertheless, currently it is guaranteed no binary-incompatible parts are depended on. Therefore, it is optional to update the binary dependencies. However, after the update of the build environment, usually the script needs to run again, to ensure up-to-date tools and libraries are installed. If the binary dependencies are no longer existing, the script will automatically fetch them from the network.
The following environment variables controls the behavior of the script:
SHBuild_BuildOpt: The build options, defaulted to -xj,$(nproc), where $(nproc) is the number of parallel builds.SHBuild_SysRoot: The root directory for the installation, defaulted to "3rdparty/YSLib/sysroot".SHBuild_BuildDir: The directory for intermediate files during the installation, defaulted to "3rdparty/YSLib/build".SHBuild_Rebuild_S1: If not empty, specify the rebuild of stage 1 SHBuild (zh-CN) (it might be slow).
3rdparty/YSLib/Tools/Scripts requires the rebuild to prevent imcompatibility.parallel command.Using of the installed binary tools and dynamic libraries requires the configurations of paths, as:
# Configure PATH.
export PATH=$(realpath "$SHBuild_SysRoot/usr/bin"):$PATH
# Configure LD_LIBRARY_PATH (reqiured for Linux with non-default search path).
export LD_LIBRARY_PATH=$(realpath "$SHBuild_SysRoot/usr/lib"):$LD_LIBRARY_PATH
The export commands can be put into the initialization scripts of the shell (such as .bash_profile) so there are no need to repeat.
After the configuration of the build environment, run the script sbuild.sh to build this project.
This method support parallel builds and different configurations compared to the direct building script, as:
./sbuild.sh release-static -xj,$(nproc)
The command above specify the output built files in the directory build/.release-static. To avoid the confilicts with intermediate files, the output executable files always having the suffix .exe.
Here, release-static is the configuration name.
Let non-empty configuration name $CONF, when $SHBuild_BuildDir is not empty, the output directory is SHBuild_BuildDir/.$CONF; otherwise, the output directory is build/.$CONF .
When $CONF has the prefix by debug, the debug versions of the libraries (already built from 3rdparty source in the previously installation steps for the build environment configuration) are used automatically, otherwise libraries of non-debug version are used. When $CONF has the suffix static, static libraries are used instead of dynamic libraries. The use of dynamic libraries makes the output executable file relying on the files in $LD_LIBRARY_PATH set up previously.
Running the direct building script links against static libraries. This is roughly equivalent to the non-debug static library builds here.
The interpreter executable file using of the dynamic library configuration relies on the correspoinding dynamic library files at runtime. It is necessary to ensure these libraries files can be found by the system (which should be prepared by the steps of the above build environment configuration), as:
# MinGW32
export PATH=$(realpath "$SHBuild_SysRoot/usr/bin"):$PATH
# Linux
export LD_LIBRARY_PATH=$(realpath "$SHBuild_SysRoot/usr/lib"):$LD_LIBRARY_PATH
If LLVM is installed to non-default location by means other than the system package manager, it may need also to configure for LLVM, as:
# Linux
export LD_LIBRARY_PATH=/opt/llvm70/lib:$LD_LIBRARY_PATH
The LD_LIBRARY_PATH in Linux configurations above can also be configured by other ways instead, such as ldconfig.
Use of static library instead can avoid the necessity of the configurations above. However, LLVM may be deployed only with dynamic libraries.
CAUTION There is no guarantee to ensure the compatibility among external binary dependencies not configurated by the scripts. They may need other reliable ways of deployment, e.g. by the system package manager. Relying on such libraries causes the final executable of the interpreter not portable among different system enviornments (like different Linux distrobutions).
Running the executable file of the interpreter enters the REPL, and the interpreter run in the interactive mode. Alternatively, specify a script name in the command line, then the interpreter will be run in the scripting mode, and the script will be loaded and executed. The script name - is treated as the standard input.
Running the interpreter with the command line option -e accepts string arguments to specify the code being evaluated before entering the interactive or scripting mode. The option -e can occur multiple times with one string argument for each instance. These strings are treated as Unilang source code and to be evaluated in order.
The command line of interpreter also supports the POSIX convention of --, which indicates all command line arguments after are not interpreted as command line options. This allows to specify script names same to options.
The commond line option -h or --help shows the help message of the interpreter.
Optionally, the environment variables are handled by the interpreter:
ECHO: If not empty, enable REPL echo. This makes sure the interpreter prints the evaluated result after each interaction session.UNILANG_NO_JIT: Disable JIT compilation, using pure interpreter instead.UNILANG_NO_SRCINFO: Disable source information for diagnostic message output. The source names are still used in the diagnostics.UNILANG_PATH: Specify the library load path. See the descriptions of standard library load in the [language specifciation (zh-CN)], as well as the descriptions of standard library operations in the implementation document of the interpreter (zh-CN).Except the option -e, with the external echo command, the interpreter can support non-interactive input, such as:
echo 'display "Hello world."; () newline' | ./unilang
A Unilang prograrm using QtWidgets is provided.
./unilang demo/qt.txt
See demo/qt.py for the Python implementation with the equivalent functionality.
There is another demo using QtQuick as a minimalized version like the Qt official qmlscene tool:
./unilang demo/qml.txt
This demo loads the source demo/hello.qml relative to the current working directory. Stay the root directory of the repository should be OK.
./unilang demo/quicksort.txt
The file test.sh is the test script. This can be directly with a few test cases. The script will call the interpreter internally.
The test cases are specified in the script code, including call the interpreter to run the test program test.txt. In REPL, load "test.txt" also call to load the test program.
The script supports following environment variables:
UNILANG: Specify the path of the interpreter, defalted to ./unilang.PTC: If not empty, the PTC test case is run. This requires manually termination of the process. During the running, a correct PTC implementation ensures the memory footprint of the intpreter eventually not grow over time.The built executables built usingsbuild.sh are not in the current working directory. This can be called by the test script as the following command:
env UNILANG=build/.debug/unilang.exe ./test.sh
The file valgrind.sh calls the interpreter via valgrind for testing purpose.
The script uses Callgrind to collect performance data by default. The data can be viewed using kcachegrind.
Use environment variables to specify the command used to invoke the interpreter and to control the options, like:
env UNILANG=build/.release-l/unilang.exe OUTPUT=build/.release-l/callgrind.out LOGOPT=--log-file=build/.release-l/callgrind.log ./valgrind.sh -e ''
Variables may have default values if not explicitly specified. The command above is actually eqivalent to the direct call to ./test-valgrind.sh -e '' without specifying any environment variables explicitly.
See the source code of the script for other supported variables and the default values of the variables.
See the examples introduction of Unilang (zh-CN) (not all of them are yet supported) the feature list document (zh-CN) for features.
See also the language specification (zh-CN) and the design and the implementation document of the interpreter (zh-CN).
Output of imprecise numbers are compatible to the format of C++ functions in <cstdio>, and it is not guaranteed to have expected lexical notations when non-default locales are used:
..Currently, the output number under non-default locales may not be successfully parsed as the numerical literals.
We encourage reporting issues and contributing modifications to the project.
See Contribution Guidelines for Developers for basic information.
However, the following probably different rules are more applicable in this project:
[optinal scope: ]<description>.
: is optional and it appertain to the scope. should be used for alignment.
u8 literals, so some code may have different meaning and it shall not be used here.-std=c++11 -pedantic-errors.{ and } shall be in one line by itself, except those consist a braced-init-list or the outermost block of a lambda-expression.
INC_Unilang_ is reserved to the header file guards.PascalCase.namespace directives shall not be used (using namespace are only allowed for internal implementations).typedef shall be used.
using instead.\ shall be used instead of @.* or / shall be used together.
//! rather than ///.en-US locale.en-US.. in the filename, before the prefixed . and file extension (if any).- and ISO 3166-1 code.en-US edition omits .en-US. For instance, README.md is the filename of the translated edition in en-US locale. The corresponding edition in zh-CN is README.zh-CN.md.Unilang is released under BSD-2-Clause-Patent.