Introduction
Welcome to the Dewy Programming Language Book. This book covers all aspects of the language, including syntax, style, the standard library, examples, etc.
Please note that this book and the language are still VERY work in progress. The book is still missing many chapters, and the current language implementation is very incomplete.
What is Dewy
The Dewy Programming Language is a simple yet powerful general purpose language designed with engineering applications in mind. Think the functionality and ease of use of matlab or python combined with the speed of a compiled language like C or Rust
Who is Dewy for
Dewy is for everyone! Dewy is designed to be easy to learn and use, while still being powerful enough to be used for real world applications. Dewy is designed to be a general purpose language, and can be used for anything from simple scripting to complex engineering applications.
Features
Dewy has many unique and uncommon features allowing it to be powerful and easy to use. Some key features include:
- Functional and Imperative - Dewy is an imperative language with strong support for functional programming. This allows for a very flexible programming style, where you can use the best tool for the job.
- Expression based syntax - Dewy uses an expression based syntax, meaning that everything is an expression. This allows for a very simple yet powerful syntax, where common language features often are just a free consequence of the syntax
- Garbage-collector-free memory management - Dewy uses a unique memory management system, allowing for fast and efficient memory management without the need for a garbage collector.
- Strong type system - Dewy has a powerful static type system with inference, reminiscent of those in Typescript and Julia.
- Built in unit system - Dewy has a built in unit system, allowing you to easily work with units and convert between them. This is especially useful for engineering applications.
- Strong math support - Dewy has strong support many math features, including complex numbers, quaternions, vectors, matrices, and more. This is especially useful for engineering applications.
Getting Started
Dewy is still in it's early development stages, but you can try it out! Currently this chapter explains how to use the current python interpreter implementation:
- Online interpreter
- Installing the interpreter on Linux
- Writing hello world
Long term, this chapter will explain how to use the compiler, and provide several hello world examples for various different applications of Dewy.
- Installing the compiler on Linux, Windows, and Mac
- Writing hello world
- Simple examples for other domains
Online Interpreter
This is a simple online interpreter for the Dewy programming language. It's very work in progress, and only supports a subset of the language features, but it should give a good idea of what the language is like.
Long term, this will be replaced with a less janky version that supports the full language.
Installation
Currently, only the python interpreter backend is available. To install it, you must have python 3.11 or later installed. Then to install:
- Clone the repo
$ git clone git@github.com:david-andrew/dewy-lang.git
- Run the install script
$ cd dewy-lang $ python install.py
- Log out and back in
Verify Install
When you do have the language properly installed, you should be able to verify that it works like so:
$ dewy -v
Dewy 0.0.0
Hello, World!
It's traditional in most languages to write a small program that prints "Hello, World!" to the screen. Achieving this is super simple in Dewy!
Put Your Code in a Directory
It's probably a good idea to put your code in a dedicated folder.
$ mdkir ~/code
$ cd ~/code
$ mdkir hello_world
$ cd hello_world
Write the Source Code
Next we'll create the source file. In a text editor of your choosing, create a file called hello.dewy
.
Then in the text editor, enter the following code
When you are done in the text editor, save and close the file.
Run the Code
Running a dewy file is as simple as invoking the file with the dewy
command
$ dewy hello.dewy
Which should print Hello, World!
in the terminal.
How it Works
This code invokes the printl
function with the string 'Hello, World!'
. printl
is a commonly used function that takes text and prints it to the terminal, followed by a newline.
Compiling and Running Are the Same Step
NOTE: this is not relevant until the LLVM/other compiler backends are implemented.
When you run the program, you are actually doing two things: first compiling, and then running.
Compiling is the process that translates the code from Dewy, which your computer doesn't understand natively, to machine language which it does understand. The resulting translation is saved to a file, called an executable, that your computer can run directly. Once the executable is created, the dewy
command then automatically runs it for you.
All of this goes on under the hood, so you don't have to worry about it. But you might notice the effects of this process, e.g. the first time you run a program, it might take a bit longer than subsequent runs. Additionally, you might notice a hidden directory containing the executable, and perhaps other files related to the compilation process. In this case, the directory is called .hello/
and contains the executable hello
.
Hello, Many Worlds!
This chapter contains short example programs over a variety of different domains--basically a Hello World for each problem. Each of these should serve as a quick start guide for common tasks in Dewy.
- GUI: creating a window with a click counter
- Graphics: drawing a triangle from scratch
- Audio: playing a sin wave through the speakers
- Networking: building a simple client/server chat program
- 2D Game Development: making a 2D Flappy Bird clone
- 3D Game Development: making a 3D Racing Game
- Web Development: building a simple website
- Databases: creating a simple data store
- Cryptography: encrypting and decrypting a string
- Operating Systems: running Hello World on bare metal
- Compilers: building a toy compiler
- Scientific Computing: making an Infinite Zooming Mandelbrot Set
- Robotics: Forward and inverse kinematics of a 6-DoF robot arm
- Machine Learning: training a simple MLP on MNIST
GUI
(TODO: creating a window with a click counter)
Graphics
(TODO: drawing a triangle from scratch)
Audio
(TODO: playing a sin wave through the speakers)
Networking
(TODO: building a simple client/server chat program)
Game Development
(TODO: making a 2D Flappy Bird clone)
Game Development
(TODO: making a 3D Racing Game)
Web Development
(TODO: building a simple website)
Databases
(TODO: creating a simple data store)
Cryptography
(TODO: encrypting and decrypting a string)
Operating Systems
(TODO: hello world boot loader program on raspberry pi)
Compilers
(TODO: building a toy compiler. perhaps something stack based, e.g. simplified porth. or perhaps something like a toy dewy/c/etc. compiler.)
Scientific Computing
(TODO: Making an Infinite Zooming Mandelbrot Set)
Robotics
(TODO: forward and inverse kinematics of a 6DoF robot arm using homogeneous transformation matrices)
Machine Learning
(TODO: training a simple MLP on MNIST)
Language Features
Each subsequent section explains a different facet of the language.
Expressions, Statements, and Blocks
Expressions
Dewy is an expression based language. An expression is literally just a value, or something that evaluates to a value. Results of expressions can be stored in variables, or used to build up more complicated expressions.
The simplest type of expression is any literal value, such as an integer for instance
This expression can easily be bound to a variable
Calling a function is an expression if the function returns a value. For example, the sqrt
function returns the square root of a value
And now my_expression
contains the value 8
.
Expressions can also be used to build up more complicated expressions
In this example, at the highest level, there is a string expression, which contains a nested expression. The nested expression sqrt(64) + 9 * cos(pi)
is a mathematical expression, built up from smaller expressions combined with math operators +
and *
. sqrt(64)
and cos(pi)
are both a function call expressions, and 64
, 9
are literal expressions and pi
is an identifier for a constant value.
Statements
A statement is a single piece of code that expresses no value (typically referred to as void
). For example calling the printl
function, which prints out a string to the console
This function call doesn't return a value. If you tried to store the result into a variable, you'd get a compilation error
Most expressions in Dewy will return something, but you can easily convert an expression into a void
statement by appending a semicolon ;
to the end of the expression
In this example, the resulting value of each sqrt
call is suppressed by the semicolon, and the array captures only the non-suppressed values, resulting in my_expression = [2 4 8]
.
Note: the one context where semicolon does not suppress the value of an expression is in a multidimensional array literal. In this context, semicolons are used to indicate new dimensions of the array, and values with semicolons are still captured.
Blocks
A block is just a sequence of expressions or statements wrapped in either {}
or ()
. A block is itself an expression.
Note: the distinction between
{}
and()
blocks has to do with the scope of the block. Any expressions inside a{}
block receive a new child execution scope, while those inside a()
block share the same scope as the parent where the block is written. Scope will be explained in greater detail later (TODO: link)
Let's start with the simplest type of a block, the empty block
Empty blocks have type void
since they don't contain any expressions, thus making the overall block not express anything.
Adding a single expression to a block makes the block itself express that value
Adding multiple expressions to a block makes the block express multiple values (TODO: link to generators)
TODO->rest of explanation of blocks.
- catching values expressed in blocks
- blocks for precedence overriding
- blocks work anywhere an expression is expected
Units
Dewy was designed from day 1 to include physical units such as kilogram, meter, second.
A Simple Example
Using units is quite straightforward, as they are just another expression. Juxtaposing a unit with a number will multiply the number by the unit, and you can use this to build up more complex expressions.
The energy
variable now contains a value of 9000 joules
. For more complex unit expressions, sometimes it is necessary to use parentheses to group terms together. In general it is good style to do so, except for the simplest unit expressions. See Operator Precedence.
Here are several more examples of unit expressions:
SI Prefixes
Note: SI prefixes only work for SI base and derived units (and a few exceptions noted below). Also the abbreviated forms of prefixes may only be combined with abbreviated units, and written out prefixes may only be combined with written out units. E.g.
kilograms
andkg
are valid, butkgrams
andkilog
are invalid.
Prefix | Abbrev. | Scale |
---|---|---|
yotta | Y | 10^24 |
zetta | Z | 10^21 |
exa | E | 10^18 |
peta | P | 10^15 |
tera | T | 10^12 |
giga | G | 10^9 |
mega | M | 10^6 |
kilo | k | 10^3 |
hecto | h | 10^2 |
deca | da | 10^1 |
deci | d | 10^−1 |
centi | c | 10^−2 |
milli | m | 10^−3 |
micro | μ /u | 10^−6 |
nano | n | 10^−9 |
pico | p | 10^−12 |
femto | f | 10^−15 |
atto | a | 10^−18 |
zepto | z | 10^−21 |
yocto | y | 10^−24 |
Non-SI units that may receive SI prefixes:
psi
(e.g.kpsi
=1000(psi)
)torr
(e.g.mTorr
=0.001(torr)
)bar
(e.g.mbar
=0.001(bar)
)eV
(e.g.keV
=1000(eV)
)cal
(e.g.kcal
=1000(cal)
)- (TODO: probably more)
Binary Prefixes
Note: These prefixes are exclusively for use with units of information (e.g.
bit
/byte
)
Prefix | Abbrev. | Scale |
---|---|---|
kibi | Ki | 2^10 |
mebi | Mi | 2^20 |
gibi | Gi | 2^30 |
tebi | Ti | 2^40 |
pebi | Pi | 2^50 |
exbi | Ei | 2^60 |
zebi | Zi | 2^70 |
yobi | Yi | 2^80 |
Full List of Units
(TODO->maybe like solidworks, allow user to set unit system, e.g. meters-kilograms-seconds, centimeters-grams-seconds, etc. See: https://en.wikipedia.org/wiki/MKS_system_of_units https://en.wikipedia.org/wiki/Metre%E2%80%93tonne%E2%80%93second_system_of_units https://en.wikipedia.org/wiki/Foot%E2%80%93pound%E2%80%93second_system https://en.wikipedia.org/wiki/Centimetre%E2%80%93gram%E2%80%93second_system_of_units )
Base Units
Note: abbreviated units and prefixes are case sensitive, while fully written out units and prefixes are case insensitive
Quantity | Symbol | Abbrev. Units | Full Units |
---|---|---|---|
Mass* | [M] | g k lbm - | gram /grams kilo /kilos pound-mass /pounds-mass slug /slugs |
Length | [L] | m - ft yd mi - AU - - | meter /meters /metre /metres inch /inches foot /feet yard /yards mile /miles nautical_mile /nautical_miles astronomical_unit /astronomical_units light_year /light_years parsec /parsecs |
Time | [T] | s - - - - - - - - - | second /seconds minute /minutes hour /hours day /days week /weeks month /months year /years decade /decades century /centuries millennium /millennia |
Electric Current | [I] | A | amp /amps /ampere /amperes |
Thermodynamic Temperature | [Θ] | K °R /°Ra °C °F | kelvin rankine /degrees_rankine celsius /degrees_celsius fahrenheit /degrees_fahrenheit |
Amount of Substance | [N] | mol | mole /moles |
Luminous Intensity | [J] | cd | candela /candelas |
(TODO: metric vs us vs etc. tons)
Note: in SI, the base unit for mass is
kg
/kilograms
, notg
/grams
.k
/kilo
is provided as a convenience to allow for a mass base unit without a prefix. e.g.kilokilo
would be equivalent to1000(kilograms)
.
(TODO: exact durations of longer units. e.g. sidereal day vs solar day, etc.)
Note: the plural of
kelvin
iskelvin
, notkelvins
Named Derived Units
Quantity | Abbrev. Units | Full Units |
---|---|---|
Plane Angle | rad ° | radian /radians degree /degrees |
Solid Angle | sr | steradian /steradians |
Frequency | Hz | hertz |
Force / Weight | N lb /lbf | newton /newtons pound /pounds /pound-force /pounds-force |
Pressure / Stress | Pa atm bar psi torr mmHg inH2O | pascal /pascals atmosphere /atmospheres bar pounds_per_square_inch torr millimeters_of_mercury inches_of_water |
Energy / Work / Heat | J cal Cal *BTU eV Wh erg | joule /joules calorie /calories kilocalorie /kilocalories british_thermal_unit /british_thermal_units electron_volt /electron_volts watt_hour /watt_hours erg /ergs |
Power / Radiant Flux | W hp | watt /watts horsepower |
Electric Charge / Quantity of Electricity | C | coulomb /coulombs |
Voltage / Electrical Potential / EMF | V | volt /volts |
Capacitance | F | farad /farads |
Reistance / Impedance / Reactance | Ω | ohm /ohms |
Electrical Conductance | S | siemens |
Magnetic Flux | Wb | weber /webers |
Magnetic Flux Density | T | tesla /teslas |
Inductance | H | henry /henries |
Luminous Flux | lm | lumen /lumens |
Illuminance | lx | lux /luxes |
Radioactivity (Decays per unit time) | Bq | becquerel /becquerels |
Absorbed Dose (of Ionizing Radiation) | Gy | gray /grays |
Equivalent Dose (of Ionising Radiation) | Sv | sievert /sieverts |
Catalytic Activity | kat | katal /katals |
Note:
Cal
is equivalent tokcal
orkilocalorie
(i.e.1000(calories)
).
Weird Units
(TODO->all other units + weird units. e.g. drops)
Other Units
Quantity | Abbrev. Units | Full Units |
---|---|---|
Information | b /bit B /byte | bit /bits byte /bytes |
(TODO: where do decibels go? B
is already taken by byte
... perhaps the user can select what units get imported by importing units from different domains, e.g. import units from si
or import units from information
)
(TODO: other units to add: Hz
, liter
, gallon
, oz
, phon
/lufs
/other sound intensity related units)
String Interpolation
Including variable values inside of a string is handled with string interpolation.
which will print the string I am 24 years old
. Any arbitrary expression can be contained inside of the curly braces. For expressions that are not a string by default, the __str__
method will be called on them to get the string version.
Basic Data Types
Numeric
Numeric data is what it sounds like, values that represent a number
Numbers
Numbers are the base case for numerical values, with each subsequent type being a more specific / restricted version of the number class.
Integers
Integers are numbers that do not contain any decimal component. By default, integers can be arbitrarily large, but fixed width integers are also possible
The full list of integer types includes
Type | Description | Range |
---|---|---|
int | Arbitrary precision signed integer | (-inf..inf) |
int8 | 8-bit signed integer | [-128..127] |
int16 | 16-bit signed integer | [-32768..32767] |
int32 | 32-bit signed integer | [-2147483648..2147483647] |
int64 | 64-bit signed integer | [-9223372036854775808..9223372036854775807] |
int128 | 128-bit signed integer | [-170141183460469231731687303715884105728 ..170141183460469231731687303715884105727] |
uint | Arbitrary precision unsigned integer | [0..inf) |
uint8 | 8-bit unsigned integer | [0..255] |
uint16 | 16-bit unsigned integer | [0..65535] |
uint32 | 32-bit unsigned integer | [0..4294967295] |
uint64 | 64-bit unsigned integer | [0..18446744073709551615] |
uint128 | 128-bit unsigned integer | [0..340282366920938463463374607431768211455] |
Custom Ranged Integers
You can create integer types with a custom range by specifying the range as part of the type annotation
TBD for behavior when value goes out of bounds. Perhaps result will be undefined, or there can be a type setting for wrap around
Fixed Point
Fixed point will be stored as two integers, digits
and shift
where the value is digits * 10^shift
TBD on the syntax for declaring a fixed point number. likely to be a function call e.g.
Rational
Rational numbers are stored as two integers, the numerator
and the denominator
, where the value is numerator / denominator
TBD on the syntax for declaring a rational number. likely to be a function call e.g.
Real
Real numbers are positive and negative numbers that can have a decimal component to them. The default real will be stored as a float64
i.e. a 64-bit floating point number, but other widths (and potentially arbitrary precision) are possible
Boolean
Standard true/false type
Complex
complex numbers
Quaternions
Quaternions
MISC.
Other datatypes (probably include on this page)
- strings
- symbolics
- units
- types
- enums or tokens
Container Types
Container types are things like arrays, dictionaries, and sets. all containers are specified using square brackets []
, while the contents (or other factors) determine the type of container
Arrays
An array is simple a list of values inside a container
Note: values do not need commas to separate them. Also arrays can contain objects of different types, though arrays where all values are just a single type will be more efficient. Arrays are 0-indexed (with potentially the option to set an arbitrary index)
TODO->explain how to make matrices and other linear algebra stuff.
Dictionaries
A dictionary is a list of key-value pairs
Again note the lack of a need for comma separation between key-value pairs.
Additionally if you wish, you can define a bi-directional dictionary using a double-ended arrow:
Note: when creating a bidictionary, every arrow must by double-ended. As new elements are added, the bidictionary will maintain the bidirectional links between each element. Regular dictionaries will not maintin such links.
Sets
A set is an unordered collection of elements
Objects
See the entry on Object and Class Types for more details. But breifly, an object can be created by wrapping declarations in a container
Ranges
A range represents some span over a set of values. Typically ranges will be over numbers, however any orderable set could be used for a range (e.g. strings, dates, etc.). Ranges are frequently used for loops, indexing, and several other places.
Ranges always contain a ..
and may include left and or right values that are juxtaposed, optionally specifying the bounds and step size.
Range Syntax
The syntax for ranges is inspired by Haskell syntax for ranges:
Like in haskell, you can use a tuple to include a second value to specify the step size.
Note:
[first..2ndlast,last]
is explicitly NOT ALLOWED, as it can have unintuitive behavior, and is covered by[first,second..last]
.
In addition, ranges can have their bounds be inclusive or exclusive. Inclusive bounds are indicated by square brackets, and exclusive bounds are indicated by parenthesis. The default is inclusive bounds. Also left and right bounds can be specified independently, so you can have a range that is inclusive on the left and exclusive on the right, or vice versa.
Juxtaposition
The left and right values are only considered part of the range if they are juxtaposed with the ..
. Values not juxtaposed with the range are considered separate expressions.
Note: the range juxtaposition operator has a quite low precedence. Most operators will have higher precedence, meaning the left and right expressions don't need to be wrapped in parenthesis. However, due to range juxtapose's low precedence, any range as part of an
in
expression (e.g.a in [A..B]
) must be wrapped in range bounds (i.e.[]
,(]
,[)
,()
) to ensure the range is parsed correctly.a in A..B
will be parsed as(a in A)..B
.
Multiple ranges can be used to index into multidimensional matrices
Numeric Ranges
Probably the most common type of range will be a numeric ranges which describes a span over real numbers.
Some simple examples include:
Ordinal Ranges
Ranges can also be constructed using any ordinal type. Currently the only only built in ordinal type other than numbers would be strings.
For example, the following range captures all characters in the range from 'a'
to 'z'
inclusive
All alphabetical characters might be represented like so
But I probably won't just be limited to individual characters. In principle you ought to be able to do something like this
which would create a range that consists of every possible 5 letter combination starting from the word 'apple'
and iterating through to the word 'zebra'
.
Note: this is distinct from every dictionary word in that range, as it will include many many gibberish words.
TDB exactly what criteria will be used for ordering strings, as I like string orderings that respect numbers embedded in them (e.g. 'apple2'
should come before 'apple10'
), but that becomes difficult with arbitrary strings. perhaps there might be a macro setting for the ordering type used
Range Uses
Ranges have a variety of different uses in Dewy.
Ranges in Loops
Probably the most common use is in conjunction with loops as a sequence to iterate over:
The above will print '0 1 2 3 4 5 '
.
To iterate over values in reverse, you can specify a reversed range:
This prints '5 4 3 2 1 0 '
.
Note: when specifying a reversed range, you must include the step size. Forgetting to specify the step size will result in an empty range, as ranges are normally increasing.
Range Arithmetic
Ranges can be used in arithmetic expressions, often as a way to construct new ranges.
Both of which will print '0 0.25 0.5 0.75 1 '
These are both equivalent to directly constructing the range [0,0.25..1]
, however the arithmetic versions are frequently more convenient.
This type of construction is closely related to the linspace()
/logspace()
functions in Dewy. TBD but linspace
/logspace
may in fact be implemented like so:
Compound Range Construction
Additionally you can construct more complex ranges by combining together multiple ranges:
The same range as above can be constructed using subtraction
Interval Membership
You can also check if a value falls within a specified range
Indexing Sequences
ranges can be used to select values from a sequence. For example, say we want a substring we can do the following
This works for any sequence type.
Note: only integer ranges can be used to index into sequences (TBD if this might be relaxed to real valued ranges).
Also, because array access is a juxtapose expression, we can easily make the selection inclusive or exclusive on either side.
You can specify that a range continues to the end of the sequence, or starts from the beginning by omitting the value for that side. This will construct a range that goes to infinity in that direction, which will select all elements in that direction.
Paired with the special end
token which represents the index of the last element in a sequence, this provides the means to select any desired subset of a sequence.
Object and Class Types
Object types are basically just containers containing assignments of variables and functions
You can access the members of the object using the .
accessor
To create an object constructor (like many languages have classes that you can instantiate) we create a function that returns an object
You can also store functions inside of objects, allowing objects to completely cover regular object behaviors from other languages
There will not be any sort of this
or self
parameter as in other languages to access an objects members from within itself/any contained functions. Instead because the functions are at the same scope as the declaration of the objects members, those members are available to the function.
If we remove any unnecessary syntax, the shorthand for constructing an object looks like this:
that is, just a function that returns an object literal, no need for braces or return
.
Dunder Methods
Similar to python, objects can define custom so-called "double-underscore" or "dunder" methods, which hook into the language's built-in functionality.
Though actually for __add__
, it might make more sense for it to be global, and you add an alternate that gets dispatched on rather than including it in the object itself:
(TODO: longer explanation)
Function Types
Functions are first class citizens in Dewy. In fact many concepts from functional programming are included in Dewy, as they frequently allow for cleaner and more concise code.
Function Literals
To create a function, simply bind a function literal to a variable
A function literal consists of the arguments, followed by the =>
operator, followed by a single expression that is the function body. In the above example, the function takes no input arguments, and doesn't return any values. Instead is simply prints a string to the terminal.
Here's an example that takes two arguments
In fact we can simplify the above function's declaration quite a bit since blocks return expressions present in the body.
When there is a single argument, you may omit the parenthesis around the argument list
Zero arguments functions require an empty pair of parenthesis:
Default Arguments
Function arguments can have default values, which are used if the argument is not specified in the function call.
Calling functions
TODO
- calling a function with name, parenthesis, and args
- functions with no arguments can omit the parenthesis
Optional, Name-only and Positional-only Arguments
TODO
- also explain about overwriting previously specified arguments (e.g. from partial evaluation, or in the same call)
Scope Capture
TODO
- what variables are available to a function's body
Partial Function Evaluation
First note that if you want to pass a function around as an object, you need to get a handle to the function using the @
("handle") operator.
If you don't include the @
operator, then the evaluation of the right-hand side would be stored into the left side
what happens is my_func
prints out "foo" to the command line, and then since it returns no value, reference_to_my_func
is not able to be assigned, causing a compiler error. We'd also get a compiler error if my_func
required arguments, as we essentially are trying to call my_func without an arguments.
Now, using the @
operator, we can not only create a new reference to an existing function, but we can also apply arguments to the reference. What this means is we can fix the value of given arguments, allowing us to create a new function.
Here we've created a new function add5
which takes a single argument, and return the result of that argument plus 5.
TODO->explain about overwriting arguments.
Operators
Dewy is a 100% expression-based language, meaning everything is formed from small pieces combined together with operators. Dewy has 3 types of operators:
- unary prefix: come before the expression
- binary infix: come between two expressions
- unary postfix: come after the expression
Binary Operators
(TODO: this is missing several operators, and may have some extra ones that are no longer planned)
Basic Math Operations
+
plus-
minus*
multiply/
divide%
modulus^
exponent
logical and bitwise operations
Note: these are logical if both operands are boolean, otherwise they are bitwise and operate on as many bits as the size of the largest operand
and
both are trueor
either are truexor
exactly one is truenot
invert (unary)nand
either is falsenor
both are falsexnor
both are false or both are true
bit-shift operations
<<!
rotate left through carry bit!>>
rotate right through carry bit<<<
rotate left no carry bit>>>
rotate right no carry bit<<
shift left (arithmetic and logical are the same for left-shift)>>
shift right (arithmetic vs logical determined by whether signed or unsigned)
boolean returning operations
=?
equal>?
greater than>=?
greater than or equal<?
less than<=?
less than or equalin?
is a member of
colon operator
:
apply a type annotation
dictionary pointers
->
indicates the left expression points to the right expression in the dictionary
<->
indicates that left is inserted as a key that points to right, and right is inserted as a key that points to left
Function pointer
=>
used for declaring a function literal
handle operator
@
return a handle to the function or variable@?
(probably) check if two references are point to the same thing
Assignment operators
=
binds the righthand expression to the lefthand identifier as a statement (i.e. nothing is returned):=
(walrus operator) same as normal assignment operator, but also returns the righthand side as an expression available for use.
Juxtaposition
(TODO: explain how juxtaposition works)
Unary Prefix Operators
(TODO: prefix operators)
Unary Postfix Operators
(TODO: postfix operators)
In-place Assignment
any of the logical/bitwise operators, as well as the boolean returning operators can be preceeded by not
which will then cause the inverse of the operation to be returned. e.g. not and
is equivalent to nand
, not <?
is equivalent to >=?
, etc.
most binary operators can be appended with an =
sign to make them into an assignment
e.g.
(TODO: This should also probably be able to be combined with element-wise/vectorized .
operations where each element in the list is updated according to the operation (can be done in parallel))
Elementwise Operations
the elementwise operator .
can be prepended to most binary operators to make it be performed on each element in a array or sequence
e.g.
This works if either the either first operand is a list, or the second is a list, or both are lists with the exact same shape
Precedence
Every operator has a precedence level, and an associativity. The precedence level determines the order in which operators in a compound expression are evaluated. Associativity determines the order of evaluation when multiple operators of the same precedence level are present in a compound expression. Associativity can be:
- left-to-right
- right-to-left
- prefix
- postfix
- none (typically these expressions generate a single node in the AST)
- fail (i.e. the expression is invalid if multiple operators of the same precedence level are present)
(TODO: some way of populating this table with the current full precedence table in the code)
(TODO: this table is missing several operators)
Precedence | Symbol | Name | Associativity |
---|---|---|---|
14 | @ | reference | prefix |
13 | . juxtapose juxtapose | access jux-call jux-index | left |
12 | ^ | power | right |
11 | juxtapose | jux-multiply | left |
10 | * / % | multiply divide modulus | left |
9 | + - | add subtract | left |
8 | << >> <<< >>> <<! !>> | left shift right shift rotate right no carry rotate left no carry rotate left with carry rotate right with carry | left |
# | in | in | fail |
7 | =? >? <? >=? <=? | equal greater than less than greater than or equal less than or equal | left |
6 | and nand & | and nand and | left |
5 | xor xnor | xor xnor | left |
4 | or nor | | or nor or | left |
3 | comma | comma | none |
# | juxtapose | jux-range | none |
2 | => | function arrow | right |
1 | = | bind | fail |
0 | else | flow alternate | none |
-1 | space | space | left |
TBD | as | as | TBD |
TBD | transmute | transmute | TBD |
TBD | |> | pipe | TBD |
TBD | <| | reverse pipe | TBD |
TBD | -> | right-pointer | TBD |
TBD | <-> | bi-pointer | TBD |
TBD | <- | left-pointer | TBD |
TBD | : | type annotation | TBD |
multi-operators e.g. 100^/2
for sqrt, or 5+-1
, etc. have a precedence at the level of the first operator in the chain (i.e. all following operators have no effect on the precedence).
Also any instances of elementwise operators (e.g. .+
.=
.xor
etc.) are at the level of precedence as the operator they're attached to. Assignment operators on the other hand are all at the same level, regardless of the type of operator they're attached to (e.g. +=
<?=
<<=
nand=
etc.)
Numbers and Bases
literal numbers in various bases can be specified using prefixes before the number
Radix | Name | Prefix | Digits |
---|---|---|---|
2 | Binary | 0b | [01] |
3 | Ternary | 0t | [012] |
4 | Quaternary | 0q | [0123] |
6 | Seximal | 0s | [0-5] |
8 | Octal | 0o | [0-7] |
10 | Decimal* | 0d | [0-9] |
12 | Dozenal | 0z | [0-9xXeE] |
16 | Hexidecimal | 0x | [0-9A-Fa-f] |
32 | Duotrigesimal | 0u | [0-9A-Va-v] |
36 | Hexatrigesimal | 0r | [0-9A-Za-z] |
64 | Tetrasexagesimal | 0y | [0-9A-Za-z!$] |
*Decimal is the default base, so the prefix is generally not necessary, unless the default base is changed.
Some examples:
See also: Base names, and Seximal
Examples
Basic Math
Linear Algebra
Functional Programming
Meta Programming
Note: This will probably not be relevant until the current handwritten parser is replaced with a parser generator (probably GLL)
Eventually, the goal is for the language to be completely described via some sort of syntax description, such as a context-free grammar. There was work on this in the past, but it was paused in favor of building a usable version of the language first. When there is a suitable parser-generator implementation of the language, one of the planned features is to include the syntax description language within Dewy itself for metaprogramming purposes. Users could describe new syntax features via the metalanguage, and then be able to use them in their programs.
Here's an example of the previous work on the metalanguage:
// This is a description of the metalanguage written in the metalanguage itself
#eps = 'ϵ' | '\\e' | "''" | '""' | "{}"; // ϵ, \e, '', "", or {} indicates empty element, i.e. nullable
#wschar = [\n\x20]; // ascii whitespace characters (restrict to newlines and spaces).
#line_comment = '/\/' '\n'~* / '\n'~; // single line comment
#block_string = ξ* - ξ* '}/'; // inside of a block comment. Cannot end with block comment delimiter
#block_comment = '/\{' (#block_comment | #block_string)* '}/'; // block comment, with allowed nested block comments
#ws = (#wschar | #line_comment | #block_comment)*; // optional whitespace sequence
#anyset = '\\' [uUxX] | [VUξ]; // V, U, ξ, \U, \u, \X, or \x used to indicate any unicode character
#hex = '\\' [uUxX] [0-9a-fA-F]+ / [0-9a-fA-F]; // hex number literal. Basically skipping the number part makes it #any
#number = [0-9]+ / [0-9]; // decimal number literal. Used to indicate # of repetitions
#charsetchar = ξ - [\-\[\]] - #wschar; // characters allowed in a set are any unicode excluding '-', '[', or ']', and whitespace
#item = #charsetchar | #escape | #hex; // items that make up character sets, i.e. raw chars, escape chars, or hex chars
#charset = '[' (#ws #item (#ws '-' #ws #item)? #ws)+ ']'; // set of chars specified literally. Whitespace is ignored, and must be escaped.
//paired grouping operators
#group = '(' #ws #expr #ws ')'; // group together/force precedence
#char = '"' (ξ - '"' | #escape | #hex) '"'; // "" single character
#char = "'" (ξ - "'" | #escape | #hex) "'"; // '' single character
#caseless_char = "{" (ξ - [{}] | #escape | #hex) "}"; // {} single character where case is ignored
#string = '"' (ξ - '"' | #escape | #hex)2+ '"'; // "" string of 2+ characters
#string = "'" (ξ - "'" | #escape | #hex)2+ "'"; // '' string of 2+ characters
#caseless_string = "{" (ξ - [{}] | #escape | #hex)2+ "}"; // {} string of 2+ characters where case is ignored for each character
#escape = '\\' ξ; // an escape character. Recognized escaped characters are \n \r \t \v \b \f \a.
// all others just put the second character literally. Common literals include \\ \' \" \[ \] \-
//post operators
#capture = #expr #ws '.'; // group to capture
#star = #expr #ws (#number)? #ws '*'; // zero or (number or more)
#plus = #expr #ws (#number)? #ws '+'; // (number or one) or more
#option = #expr #ws '?'; // optional
#count = #expr #ws #number; // exactly number of
#compliment = #set #ws '~'; // compliment of. equivalent to #any - #set
//implicit operators
#cat = #expr (#ws #expr)+; // concatenate left and right
//binary expr operators
#or = (#expr #ws '|' #ws #expr) - #union; // left or right expression
#reject = (#expr #ws '-' #ws #expr) - #diff; // reduce left expression only if it is not also the right expression
#nofollow = #expr #ws '/' #ws #expr; // reduce left expression only if not followed by right expression
#greaterthan = #expr #ws '>' #ws #expr; // left expression has higher precedence than right expression
//binary set operators
#diff = #set #ws '-' #ws #set; // everything in left that is not in right
#intersect = #set #ws '&' #ws #set; // intersect of left and right
#union = #set #ws '|' #ws #set; // union of left and right
//syntax constructs
#set = #anyset | #char | #caseless_char | #hex | #charset | #compliment | #diff | #intersect | #union;
#expr = #eps | #set | #group | #capture | #string | #caseless_string | #star | #plus | #option | #count | #cat | #or | #greaterthan | #lessthan | #reject | #nofollow | #hashtag;
#hashtag = '#' [a-zA-Z] [a-zA-Z0-9_]* / [a-zA-Z0-9_];
#rule = #hashtag #ws '=' #ws #expr #ws ';';
#grammar = (#ws #rule)* #ws;
#start = #grammar;
If/when a metalanguage is added to Dewy, it will likely look much different than this. Current drawbacks of this syntax are:
- difficulties describing expression precedence and associativity
- difficulties handling ambiguity that may arise from the grammar
- verbosity of handling whitespace/comments
- some incompatibility of the metalanguage with Dewy syntax. Ideally metalanguage expressions would be valid Dewy expressions
- currently no process for the semantic results of parsed rules
Something more ideal may make use of string prefix functions
Or perhaps a syntax constructed explicitly from valid Dewy expressions
Flow Control
In Dewy, the two main methods of conditionally executing code are if
and loop
expressions.
If Expressions
If expressions allow you to conditionally evaluate code based on whether or not some condition is met.
The syntax for an if
expression is:
where <condition>
must result in a boolean value, and <expression>
can be anything. Commonly, <expression>
will be a block containing multiple expressions.
Loop Expressions
Loop expressions allow you to repeat the execution of some code while some condition is met.
The syntax for a loop
expression is:
where <condition>
must be an expression that evaluates to a boolean value, and <expression>
can be anything.
Loops will be explored in more detail in One Loop To Rule Them All.
Flow Chains
Multiple flow expressions can be chained together via the else
operator, along with an optional final default case that need not be a flow expression. In normal languages, this would be if-else-if
kinds of sequences, which are certainly possible in Dewy:
or even
But Dewy also allows loop
expressions to be combined in this way as well:
In the above example, if a
is greater than b
, the first block would be executed, and the rest of the blocks are skipped. If a
is less than b
, the loop in the second block executes, incrementing a
until it is equal to b
, at which point the rest of the chain is skipped. Only if a
is neither greater than nor less than b
(i.e. a
equals b
) will the final block be executed exclusively.
(TODO: add an example for how all conditions share the same scope, so variables defined in one condition will be available in later bodies if they execute)
(TODO: probably add a finally
operator which can be used to always execute code at the end)
Capturing Values
Unlike if statements from other languages, if
s and loop
s in Dewy are themselves expressions, allowing any expressed values to be captured. if
expressions basically act like Dewy's version of the ternary operator
my_var
would have a value of 'a tropical fruit'
at the end of the above example.
Values from loops can be captured to construct sequences, which is explored more in One Loop To Rule Them All.
Match Expressions
(TODO) switch statement equivalent
Break, Continue, Return,
(TODO) branch inside body of conditional
Advanced Flow Control
(TODO) combining conditionals (TODO) list and dictionary generators
One Loop to Rule them All
Other languages make use of sometimes multiple keywords such as for
, while
, do-while
, for each
, etc., to handle looping over a piece of code. Dewy instead simply uses the loop
keyword to handle all forms of looping.
Syntactically, loops are quite simple. The syntax for a loop is:
Where <condition>
must result in a boolean determines if the loop continues, and <expression>
which can be anything is executed each time the loop repeats.
The various types of loops seen in other languages are formed simply by changing the <condition>
part of the loop.
Infinite Loops
An infinite loop is one that never ends. They are constructed by hardcoding the condition to true
, which ensures that the loop will always repeat.
The only way to leave an infinite loop is via the break
, and return
keywords.
While Loops
A while loop is a loop that executes "while" some condition is true. A simple boolean expression can be used as the condition. When the condition is false, the loop ends.
For Loops
Many languages feature for loops, which iterate over some iterable object. The simplest case of this would be iterating over a range of numbers. In Dewy, the in
operator manages iteration for loops. in
has two aspects:
- the variable on the left is assigned with the next value of the iterable on the right (or
void
if there are no more values) - the expression returns
true
orfalse
depending on if there was a value to assign to the variable this iteration.
This means that in
expressions can be used to trivially construct a for-loop.
Which prints out 1, 2, 3, 4, 5,
to the console. Each iteration, in
causes i
to be assigned the next value in the sequence, while returning true for the loop condition. When the sequence is exhausted, in
returns false, and the loop ends.
For loops can also iterate over the items in any type of container. Iterating over a list looks like this
Which prints the following to the console
I like to eat apple
I like to eat banana
I like to eat peach
I like to eat pear
Items in a dictionary can be iterated over like so
This takes advantage of the fact that iterating over a dictionary returns each pair, which can then be unpacked into separate variables show
and rating
. This prints out the following to the console
I give star wars a 73 out of 100
I give star trek a 89 out of 100
I give star gate a 84 out of 100
I give battlestar galactica a 87 out of 100
I give legend of the galactic heroes a 100 out of 100
Multiple Conditions
A neat side effect of in
statements returning a boolean is that it provides a free method for looping over multiple sequences simultaneously. Simply combine two in
statements with a logical operator. The loop will continue until one sequence is exhausted, both, or something else, depending on which logical operator is used. The behavior zip
from other languages can be achieved by combining sequences with and
In this case, the loop runs until either sequence is exhausted (as and
requires both conditions to be true, so as soon as one is false, the loop ends). This prints out the following to the console
Alice chose Red
Bob chose Blue
Charlie chose Green
Other languages commonly have an enumerate
function which will count how many iterations have occurred on top of looping over some sequence. This can be achieved by combining an infinite range with any sequence using and
:
i
will never run out of values, so the loop continues so long as fruit
has values remaining. This prints out the following to the console
0) apple
1) banana
2) peach
3) pear
Using the or
operator to combine sequences will loop until so long as either of the sequences have values remaining.
Which prints
[1 4]
[2 5]
[3 6]
[undefined 7]
[undefined 8]
Since this is just combining boolean expressions, any combination of expressions that results in a boolean may be used.
As a similar approach, perhaps there might be iterators in this style where they don't have a fixed value, but instead track over some changing resource
Do Loop Do
The do-while version of the loop can be constructed by putting the do
keyword before the body, and putting the loop
keyword and its condition after the body. This means loop body is executed at least once before the condition is checked, at which point the loop could exit or continue.
Basic do-while loop:
do-loop over an iterator. On the first iteration, i
will be undefined, while it will be available on subsequent iterations
Which prints
this is a do-for loop. i=undefined
this is a do-for loop. i=0
this is a do-for loop. i=1
this is a do-for loop. i=2
this is a do-for loop. i=3
this is a do-for loop. i=4
this is a do-for loop. i=5
Technically you can construct an infinite do-while loop, but it's basically identical to a regular infinite loop
Lastly you can sandwich loop
between two blocks using two do
keywords (one before the first block, and one after the loop condition). This will give you a block executed before the condition and a block executed after the condition
Note: the syntax for do-loop-do is still being finalized, and may change from this example
In this loop, the first block is guaranteed to execute at least once. Then we check the condition, and then if true, we execute the second block, then repeat the loop, execute the first block, and then check the condition again, repeating until the condition is false, or we have iterated over all elements.
Break, Continue, Return inside Loops
TODO->write this. follows basic principles of other languages. extra is that you can use #hashtags
to break/continue from inside nested loops
Loop Generators
Let's look at this example
Every iteration of the loop, the current value of i
is "expressed", that is to say, the value could be stored in a variable or a container.
Lets capture the expressed value in a container by wrapping the loop in []
brackets
This "generates" the array [1 2 3 4 5 6 7 8 9 10]
, which we can then store into a variable
And thus we have created the simplest list generator.
Multiple Expressions per Iteration
Generators can do a lot of interesting things. For example we can express multiple values on a single loop iteration
producing the array [1 1 2 4 3 9 4 16 5 25]
.
We can also construct a dictionary by expressing with a ->
between two values
which produces the dictionary [1->1 2->4 3->9 4->16 5->25]
which points from values to their squares.
Multidimensional Generators
You can generate a multidimensional array using multiple nested loops. For example
which produces the following 3D array representing the indices of a 5x5 matrix as tuples
indices = [
[[1 1] [1 2] [1 3] [1 4] [1 5]]
[[2 1] [2 2] [2 3] [2 4] [2 5]]
[[3 1] [4 2] [3 3] [3 4] [3 5]]
[[4 1] [4 2] [4 3] [4 4] [4 5]]
[[5 1] [5 2] [5 3] [5 4] [5 5]]
]
And so many more things are possible. Loop generators are about as flexible a feature as one could imagine. It's really up to you how you want to apply them
Imports
TODO
Syntax likely to change
Examples of imports
Standard Library
Dewy is 100% batteries included, and provides a comprehensive standard library over most common programming facets.
Note: This is a work in progress. The standard library is not yet implemented. For now this is just a record of the planned standard library features.
Standard Library Reference
- Data Structures
- Time
- Plotting
- TODO: more
Data Structures
Time
TODO:
- timezones
- calendars
- gregorian
- julian
- Human Era
Plotting
Work in progress
Types of plots to include out of the box:
- ridgeline plot
- TODO: more
Animating plots
TODO
Blocking vs non-blocking plots
TODO
General Concepts
Sandboxes and Harnesses
say you want to simulate running your program to test it, but without it actually having any side effects outside of the program. Sandboxes/Harnesses make this trivial. (TODO)
- when developing apps that have side effects outside of the program (e.g. file system, web traffic, etc.), sandboxes and harnesses provide a convenient way to test the program without it actually affecting anything outside of the program.
- should probably be some sort of command line argument when running a program. Can also provide code that determines the sandbox/harness response for various actions (e.g. handle what happens when the user tries to open a file, or when the program tries to send a web request, etc.). There should also be good default implementations for all aspects of the sandbox/harness.
- can also harness portions of an application while other aspects have not yet been developed. For example, if you are developing a new page on a large web app, and it itself interacts with parts that aren't implemented, you can create a harness that simulates those aspects as if they were implemented.
- harnesses can simulate different operating systems, etc.
Sandboxes/Harnesses make it so testing code is identical to running in production