Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
504 lines (386 sloc) 32 KB
{{
metadata = {
title: "Building Wee Lang (2) - History lessons",
summary: "Wherein I overanalyze QBasic and derive a basic feature set for Wee.",
image: "tom.jpg",
date: parseDate("2018/07/21 23:00"),
published: true,
}
}}
<!-- Hi! -->
{{include "../../../_templates/post_header.bt.html"}}
{{include "../../../_templates/post_header.bt.html" as post}}
{{post.figure("tom.jpg", "We're going to build an eierlegende Wollmilchsau.")}}
<p class="note">
This is the second in a series of articles on building a programming language called Wee and its tooling. Wee is an educational prgramming tool for beginners, bridging the gap between (visual) learning tools like <a href="https://scratch.mit.edu/">Scratch</a>, and professional environments like Java, Python, or C. You can learn more about my motivations on my <a href="https://www.badlogicgames.com/wordpress/?p=3950">old blog</a>.
</p>
<p>In the <a href="../dissecting-history/">last installment</a>, I reminisced about how I learned to program. For brevity's sake, I left out one important aspect: QBasic, the language. In this article, I want to informally dive into QBasic the language and then try to identify the key elements across all the factors that enabled me to learn programming, technical and non-technical. It is these key elements I want to improve upon and derive Wee from.</p>
<p>As a small aside: QBasic is a less powerful derivative of QuickBasic that shipped with MS-DOS 6.x. It lacks QuickBasic's compiler and linker, misses some standard library functions, does not include advanced IDE features like watchpoints and conditional breakpoints, and does not support QuickBasic's module system. Except for the missing module support, the languages QuickBasic and QBasic are identical. I'll hence treat them as the same in the following discussion.</p>
<h2>Q(uick)Basic</h2>
<p>A lot has been written about the mind-crippling properties of <a href="https://en.wikipedia.org/wiki/BASIC">BASIC</a>. The most prominent quote about BASIC comes from <a href="http://en.wikipedia.org/wiki/Edsger_W._Dijkstra">Dijkstra</a></p>
<p class="quote">"It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration."</p>
<p>Having famously made <a href="http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF">the case against the GOTO statement</a> and having coined the term <a href="https://en.wikipedia.org/wiki/Structured_programming">structured programming</a>, it is unsurprising that Dijkstra would have great distain for a language that is essentially <code>GOTO</code> soup, as this little <a href="https://en.wikipedia.org/wiki/GW-BASIC">GW-BASIC</a> snippet illustrates:</p>
<pre><code>10 INPUT "What is your name: "; U$
20 PRINT "Hello "; U$
30 INPUT "How many stars do you want: "; N
40 S$ = ""
50 FOR I = 1 TO N
60 S$ = S$ + "*"
70 NEXT I
80 PRINT S$
90 INPUT "Do you want more stars? "; A$
100 IF LEN(A$) = 0 THEN GOTO 90
110 A$ = LEFT$(A$, 1)
120 IF A$ = "Y" OR A$ = "y" THEN GOTO 30
130 PRINT "Goodbye "; U$
140 END
</pre></code>
<p>Early BASIC dialects like GW-BASIC supported only rudimentary forms of control flow statements. Instead, control flow was largely expressed by jumping between (mandatory) line numbers via the <code>GOTO</code> statement. These dialects also did not support functions. These are the BASICs Dijkstra was refering to.</p>
<p>But Q(uick)Basic was a different beast entirely: it was a <u>structured programming language</u>.</p>
<h3>Program Structure</h3>
<p>A QuickBasic program consists of one or more <code>.BAS</code> files (QBasic only deals with a single <code>.BAS</code> file). <u>Each file defines a module</u>. A module consists of:</p>
<ul>
<li>Forward declarations of functions, procedures, types and (module-)global variables.</li>
<li>Module-level code.</li>
<li>Function and procedure definitions.</li>
</ul>
<p>When compiling or running a program made up of multiple modules, one module is chosen as the entry point. Its module-level code becomes the main driver of the program.</p>
<p>To access functions, procedures, types and variables of other modules, they have to be declared in the module using them. This is achieved through a <code>.BI</code> header, usually one per module. This header file can then be included via the <code>$INCLUDE</code> meta-command, in both the module the declarations belong to, as well as other modules that want to access the module's content.</p>
<p>Here's a simple example of a program consisting of a main module, and a second module defining a procedure, a function, a variable shared across modules, and a type. Note that the variable is both accessible to other modules module-level code through <code>COMMON</code>, and their functions and procedures through <code>SHARED</code>.</p>
<pre><code class="basic">' --- MAIN.BAS ---
'$INCLUDE: 'MODULE.BI'
DIM p as Point2D
initModule
PRINT "Module version: " + moduleVersion
PRINT add%(1, 2)
PRINT p.x; p.y
</code></pre>
<pre><code class="basic">' --- MODULE.BI ---
DECLARE SUB initModule ()
DECLARE FUNCTION add%(a AS INTEGER, b AS INTEGER)
COMMON SHARED moduleVersion AS STRING
TYPE Point2D
x AS INTEGER
y AS INTEGER
END TYPE
</code></pre>
<pre><code class="basic">' --- MODULE.BAS ---
'$INCLUDE: 'MODULE.BI'
SUB initModule ()
moduleVersion = "1.0"
END SUB
FUNCTION add%(a AS INTEGER, b AS INTEGER)
add% = a + b
END FUNCTION
</code></pre>
<p>QuickBasic does not have the notion of <u>name spaces</u>, something sorely missed when mixing many modules. <u>Overuse of module-global and program-global variables</u> was also a "feature" of many QuickBasic programs in the wild, despite language constructs that could have helped avoid them.</p>
<p>QuickBasic modules can be combined into <u>library files</u> with the suffix <code>.QLB</code>. This allows exchanging code without giving away the source code. In addition to packaging up QuickBasic modules, you can also put native code (<code>.OBJ</code> and <code>.LIB</code>) into a QLB file. QuickBasic can call into <code>cdecl</code> and <code>pascal</code> code pretty easily. Declare the function signatures, marshall strings and arrays or pass pointers to records directly, and away you go.</p>
<h3>Types, variables, scopes and operators</h3>
QBasic supports the following types:
<ul>
<li>Primitive types
<ul>
<li><code>INTEGER</code>, for 16-bit signed integers.</li>
<li><code>LONG</code>, for 32-bit signed integers.</li>
<li><code>SINGLE</code>, for 32-bit floating point numbers.</li>
<li><code>DOUBLE</code>, for 32-bit floating point numbers.</li>
</ul>
</li>
<li><code>STRING</code>, <u>immutable</u>, fixed length or dynamic length (e.g. as the result of a concatenation), and made up of ASCII characters.</li>
<li>Arrays, multi-dimensional, with custom index ranges. Can contain elements of all types except other arrays.</li>
<li><u>Records</u>, that could contain primitive types, fixed length strings, and other record types as fields. Can not contain dynamic strings or fixed/dynamic arrays. <u>Records can not be recursive.</u></li>
</ul>
<p>The first surprise in QBasic's type system: <u>everything is a value type</u> (as far as the user is concerned, and with one exception).</p>
<p><u>Every variable is statically typed</u>, either through an explicit declaration statement or implicitely through first use and an optional type suffix.</p>
<pre><code class="basic">a% = 10 ' A 16-bit integer
b&amp; = 20 ' A 32-bit integer
c! = 1.23 ' A 32-bit float
d# = 1.23 ' A 64-bit float
DIM s as STRING * 40 ' Fixed length string of 40 characters
e$ = "Hello world." ' A dynamic string
e$ = e$ + " My name is Mario." ' Concatenation creates a dynamic new string
DIM a(-5 TO 5) AS INTEGER ' Fixed length array of integers
REDIM f(a%) AS INTEGER ' A dynamic array
' Record type declaration
TYPE Point2d
x AS SINGLE
y AS SINGLE
END TYPE
DIM p AS Point2d ' A record consisting of two 32-bit floats x and y.
</code></pre>
<p>Declaring a variable allocates the required memory to hold a value of the variable's type. The variable will point to that memory location until it goes out of scope and its memory is reclaimed (more on that in the memory management section below). The scope of a variable is either that of the program (module-level variables), or that of the function or procedure it is defined in. Qbasic does not create scopes for control flow structures.</p>
<p><u>Variables are initialized to their type's default value</u>. All primitive types are initialized to <code>0</code>. Strings are initialized to an empty string. Record fields are initialized to their respective type's default value. Array elements are also initialized to their type's default value.</p>
<p>QBasic performs <u>implicit narrowing and widening</u> for primitive types, e.g. you can assign an <code>INTEGER</code> to a <code>SINGLE</code> and vice versa. There is <u>no implicit conversion of non-string types to <code>STRING</code></u>.</p>
<p>For primitive types, the <u>binary arithmetic operators</u> <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>\</code> (integer division), <code>MOD</code> (integer modulo) and <code>^</code> (exponentiation) are defined. Unary arithmetic negation is also supported.</p>
<p>The <u>binary logical operators</u> <code>AND</code>, <code>OR</code>, and <code>XOR</code>, as well as the unary logical <u>NOT</u> are only defined on primitive types. QBasic does not have a boolean type, but instead interprets non-zero primitive values as <em>true</em> and zero as <em>false</em>.</p>
<p>The <u>binary relational operators</u> <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code>, <code>=</code> (equal), <code>&lt;&gt;</code> (not equal) are supported for primitive types and strings. In the case of strings, the operators compare each string's characters and their length. <u>Records and arrays can not be tested for equality/inequality</u>, altough this would be trivial since everything is a value type.</p>
<p>All operators discussed so far follow the <u>standard operator precedence</u> found in other languages like JavaScript. In addition to this operator precedence, users can use <code>(</code> and <code>)</code> to group sub-expressions.</p>
<p>The <u>assignment operator</u> <code>=</code> is defined for all types except arrays. Since everything is a value type, <u>assignment deep copies</u> the right-hand side value to the left-hand side variable, record field or array element.</p>
<p>The <u>array element indexing operator</u> <code>(index)</code> lets us work with array elements. Used on the left-hand side of an assignment, the right-hand side value (which must match the array element type) is deep copied to the array element at the index. Used within an expression, the operator returns a deep copy of the array element at the index.</p>
<p>The <u>field operator</u> <code>.</code> lets us access the invidual fields of a record. Used on the left-hand side of an assignment, the right hand side value (which must match the field type) is deep copied to the field of the record. Used within an expression, the operator returns a deep copy of the field value of the last element in a field operator chain, e.g. <code>a.b.c</code>.</p>
<h3>Control flow statements</h3>
<p>QBasic supports a basic set of control flow statements.</p>
<pre><code class="basic">' Conditions must evaluate to non-zero (true) or zero (false)
IF a < b AND d THEN
...
ELSEIF d <> e THEN
...
ELSE
...
END IF
WHILE a
...
WEND
DO
...
LOOP WHILE a > b
' STEP is optional
FOR i = 2 to -2 STEP -1
...
NEXT
' The test expression can be a primitive or string
SELECT CASE someString$
CASE "Hello", "World"
...
CASE IS < "Muh" ' IS is required when using relational operators
...
CASE 10 TO 20 ' Can also specify ranges if the text expression is a number
...
CASE ELSE
...
END SELECT
</code></pre>
<p>Nothing out of the ordinary, except that <code>SELECT</code> statements, that allow usage of strings, ranges and relational operators (compare to Java's <code>switch</code> and cry a little inside). Of course, QBasic also supports <code>GOTO</code>, but let's ignore that. All control flow statements are just that: statements. They do not yield values themselves.</p>
<h3>Procedures and functions</h3>
<p><u>Lacking a <code>unit</code> or <code>void</code> type</u>, QBasic has to make an explicit distinction between functions and procedures (functions "returning" void).</p>
<pre><code class="basic">SUB DoSomething (stuff%, otherStuff%)
...
END SUB
FUNCTION Repeat$(s AS STRING, repetitions as INTEGER)
FOR i% = 1 to repetitions
Repeat$ = Repeat$ + s
NEXT
END FUNCTION
DoSomething 1, 2
a$ = Repeat("Huh", 5)
</code></pre>
<p>The return type of a function is declared by suffixing the function name with one of the implicit type suffixes for primitive and string types. This means that in QBasic, <u>functions can not return records or arrays</u>! To return a value, you simply assign it to the function name.</p>
<p>Here is another interesting deviation from the "norm": <u>All arguments are passed by reference</u> by default, including primitive types. This is the exception to "everything is a value type". It is also a solution to the problem of not being able to return records and arrays: you simply pass them in by reference.</p>
<pre><code class="basic">SUB foo(i AS INTEGER, a() AS INTEGER, s AS STRING, p AS Point2d)
i = 10
a(1) = 20
s = "Hello"
p.x = 30
END SUB
DIM i AS INTEGER
DIM a(1 to 10) AS INTEGER
DIM s AS STRING
DIM p AS Point2d
foo(i, a, s, p)
PRINT i; a(10); s; p.x
' Output: 10 20 Hello 30
</code></pre>
<p>The <code>BYVAL</code> and <code>BYREF</code> keywords can be used to specify how arguments should be passed explicitely, except for array parameters, which are always passed by-reference. That makes sense, as passing an array by value could be a costly operation.</p>
<p>Speaking of arrays: array parameters are always dimensionless. Bounds and dimensionality checks are performed at runtime.</p>
<h3>Error handling</h3>
<p>QBasic has a somewhat <u>rustic way of raising and handling errors</u>. The <code>ERROR</code> statement raises an error, identified by a (user-defined) error code. If no error handler is set, the program terminates. To set the global error handler, the <code>ON ERROR GOTO label|lineNumber</code> statement is used. If an error occurs, the runtime jumps to the module-level line specified after <code>GOTO</code>, which can decide how to proceed. The error handler has access to a few globals, most notably <code>ERR</code>, which holds the error code, and <code>ERL</code>, which holds the line at which the error was raised. The error handler can either exit the program, resume at the next statement after the statement that raised the error, or jump to a label/line number. Here's how that looks like:</p>
<pre><code class="basic">SUB foo ()
ERROR 123 ' raise an error
PRINT "Resumed from error handler."
END SUB
ON ERROR GOTO ErrorHandler
CALL foo()
b% = 0
a% = 1 / b% ' Division by zero raises ERR 11
ErrorHandler:
IF ERR = 123 THEN
PRINT "Oh no, an error occured, let's ignore it."
RESUME NEXT
ELSE
PRINT "Unhandled error"; ERR; "at line "; ERL
END
END IF
' Output:
' Oh no, an error occured, let's ignore it.
' Resumed from error handler.
' Unhandled error 11 at line 10
</code></pre>
<p>Not the most sophisticated of error handling mechanisms, and a bit icky as all errors (including those of modules) need to be handled in a single place. Still better than a segmentation fault!</p>
<h3>Memory management</h3>
<p>QBasic has <u>automatic memory management</u>. For a user, that's the end of the story (safe for standard library functions like <code>CLEAR</code> that smoke any and all variables for cases where memory gets tight). Under the hood, things are more interesting</p>
<p>Primitive type values and record values work the same: they are allocated in the program's data segment (module-level code) or on the stack (by-value arguments, function/procedure-level variables). Module-level values of these types never get reclaimed. Stack allocated values are cleaned up as soon as a function/procedures frame is popped from the stack. This may partially explain why records can not contain dynamic strings or arrays, as these are handled differently (Records can contain static strings which are just embedded, fixed length arrays of bytes without additional meta-date, and hence easy to reclaim).</p>
<p>Both dynamic strings and dynamic arrays allocate their data on the heap internally. In addition to a pointer to that heap data, they also keep track of the data's length for bounds checking.</p>
<p>An array's heap data is uniquely owned by its variable. Since we can't assign one array to another, the ownership can not be transferred. Since arrays can only be passed by reference, and functions can not return an array, the invariant still holds (think this through, it's fun!). Reclaiming dynamic arrays is thus simple: if the array variable goes out of scope, free the heap memory. The second reclamation point is when an array is dynamically resized via the <code>REDIM</code> statement: the old heap data gets freed, and a new heap block is allocated an assigned to the array variable. The invariant still holds!</p>
<p>Strings require a more complex reclamation strategy, as strings can be assigned to each other. In addition to the heap pointer and length, QBasic also stores a reference count for string heap data. Since strings are immutable, their heap data can be shared by multiple string variables. When one dynamic string is assigned to another dynamic string variable, the heap data reference count of the old string data is decreased, and if no other string variable references it, reclaimed. Conversely, the heap data reference count of the assigned string is increased, and the assigned-to string variable points to that heap data going forward. The two strings now share the same heap data.</p>
<p>As it turns out, QBasic does actually work with <u>internal reference types for dynamically sized types</u>. It also achieves <u>multiple-ownership semantics by reference counting</u> for string data. Without the user noticing.</p>
<p class="note">Note: The information in this section has been pieced together from bits of information scathered around the web and inspecting the assembly output of the QuickBasic compiler. There may be some inaccuracies, but the basic principles should hold.</p>
<h3>Design consequences</h3>
<p>In my opinion, the biggest consequence of QBasic's design is that there are <u>no aliases</u>, (except when passing arguments by reference). This is a side-effect of not having reference types (again, ignoring arguments passed by reference). It's both a blessing and curse. What's aliasing, you ask?</p>
<p>Aliasing occurs when a single value is referred to by two or more symbols in the program, e.g. two variables, or a field and a variable, or through two array elements, and any other combination you can think of. It's very common in languages that have pointers or references. Consider this (pseudo) Java snippet:</p>
<pre><code class="java">class A { B b = new B(); }
A a = new A();
foo(Arrays.asList(a.b));
</code></pre>
<p>The <code>B</code> instance escapes the unique ownership of A by being also put into a list. Worse, that list is passed to a method <code>foo()</code>, and god knows what that does with our poor <code>B</code> (e.g. create more references to our <code>B</code>). Anything in the system can potentially modify <code>B</code>, which makes reasoning about state hard.</p>
<p>Having no aliases is a blessing, because <u>a system without aliasing simplifies reasoning about state and state mutators</u>. This is not only true for humans, but also compilers, which have a much easier time optimizing code without aliases. In case of QBasic, it also makes automatic memory management a lot simpler: since there can not be reference cycles, simplistic reference counting is sufficient, and only required for types that can't be allocated on the stack, e.g. dynamic arrays or strings.</p>
<p>On the other hand, having no aliasing is also a curse. Quite a few <u>fundamental data structures like graphs are cumbersome to implement without aliases</u>. In a system without reference types, we can't have recursive types. Without recursive types, we can't do something like a binary tree as easily as this:</p>
<pre><code class="java">// A binary tree node stores references to its left
// and right children via a reference, with null signaling
// "no reference".
class Node {
String value;
Node left;
Node right;
public Node (String value) { this.value = value; }
}
// Creating a tree is easy
Node root = new Node("root");
root.left = new Node("root.left");
root.right = new Node("root.right");
root.right.left = new Node("root.right.left");
// Traversing the tree is simple as well
public void traverse (Node node) {
if (node == null) return;
System.out.println(node.value);
traverse(node.left);
traverse(node.right);
}
</code></pre>
<p>Here's the equivalent in QBasic:</p>
<pre><code class="basic">' A binary tree node stores references to its left
' and right children via an index into an array, with 0 signaling
' "no reference".
TYPE Node
value AS STRING * 40
left AS INTEGER
right as INTEGER
END TYPE
' Creating a tree is cumbersome
DIM nodes(1 TO 4) AS Node
nodes(1).value = "root"
nodes(1).left = 1
nodes(1).right = 2
nodes(2).value = "root.left"
nodes(3).value = "root.right"
nodes(3).left = 4
nodes(4).value = "root.right.left"
' Traversing a tree is slightly more cumbersome
SUB traverse (nodeIndex AS INTEGER, nodes() AS Node)
if (nodeIndex = 0) RETURN
PRINT nodes(nodeIndex).value
traverse(nodes(nodeIndex).left, nodes)
traverse(nodes(nodeIndex).right, nodes)
END SUB
</code></pre>
<p>We have built our own "reference system" by abusing array indices as aliases. Rust suffers from a <a href="https://rust-leipzig.github.io/architecture/2016/12/20/idiomatic-trees-in-rust/">similar deficiency</a>, provided you want to stick to idiomatic Rust. We can also see that in a system without reference types, it's <u>impossible to "extract" sub elements of arrays or records into the local scope through an alias</u>.</p>
<pre><code class="basic">DIM node AS node
node = nodes(1) ' Let's modify the root!
node.value = "Hi!"
PRINT nodes(1).value ' Oh no, we modified a copy
' Output: root
</code></pre>
<p>But it's not all bleak. Another consequence of "everything is a value type" is the absence of the <a href="https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions">billion dollar mistake</a>. <u>There is no <code>null</code></u> in QBasic. Sadly, it also <u>lacks a mechanism to signal optionality</u> of a value.</p>
<h2>Key elements</h2>
<p>If you read this far, ping me on <a href="https://twitter.com/badlogicgames">Twitter</a> to receive your price! Time to summarize my findings.</p>
<h3>Target audience, prerequisits, motivators</h3>
<p>I did not jump head first into programming, but had exposure to basic computing first. From that, I derrive the following basic skill set, which implicitely defines the target audience of Wee. A novice user of Wee:</p>
<ul>
<li>Has a specific goal that can be achieved through programming.</li>
<li>Knows basic arithmetic and can read and write fluently.</li>
<li>Has a personal computer.</li>
<li>Knows how to write on a keyboard and how to use a mouse or touchpad.</li>
<li>Knows what files and folders are.</li>
<li>Knows that programs exist and serve different purposes (but not necessarily how they work).</li>
<li>Knows how to use a browser.</li>
<li>Extremely basic proficiency in English.</li>
</ul>
<p>The requirement of owning a personal computer may look a bit weird when phones and tablets are ubiquitous. But I do not think a tiny phone display and touch typing lend themselves well to learning to program. The limited screen space requires excessive mode switching. Worse, on-screen keyboards cover 30-50% of the available screen space, leaving less room to display helpful information. It is simply not comfortable to program on a mobile device (including tablets).</p>
<p>The basic English proficiency requirement stems from the fact, that I do not plan on localizing keywords and sample programs to other languages. My <a href="https://twitter.com/badlogicgames/status/1019187811054948352">super scientific Twitter poll</a> seems to indicate that localizing the programming language probably does more harm than good. Information in the software world is pre-dominantely transported in English, so we might as well get novices on track early. Learning materials, comments in programs, and possibly community sections should of course be localized.</p>
<p>Of course, some of these requirements can be relaxed if the user is accompanied by a person that can compensate missing skills on the spot.</p>
<h3>Learning materials</h3>
<p>When I started to program, I sourced my learning materials from various places, which was not very effective. The materials also differed in kind. The rough sketch for Wee:</p>
<ul>
<li>Remove the language barrier as much as possible, by localizing all learning materials.</li>
<li>Provide a centralized hub for all learning materials, including instructions on where and how to get help.</li>
<li>Provide guided learning materials in form of interactive tutorials, that introduce users to
<ul>
<li>A high-level overview of programming and what it can be used for.</li>
<li>The <a href="https://www.researchgate.net/publication/259998496_Notional_Machines_and_Introductory_Programming_Education">notional machine</a> model used by Wee, using an interactive visualization.</li>
<li>How to navigate and use the basic facilities of the programming environment.</li>
<li>The Wee programming language, using relatable problems to motivate the introduction of a language feature.</li>
</ul>
</li>
<li>Provide explorative learning materials in form of small programs, using descriptive naming and comments, which users can modify and remix.</li>
<li>Provide reference materials integrated in the programming environment, including small, self-contained examples that can be copied, pasted and run immediately.</li>
<li>Allow users to publish their own materials (articles, remix-able programs, comment sections on programs, ...) on the centralized hub.</li>
<li>Pointers to "where to go from here" for advanced users that want to explore professional tools.</li>
</ul>
<h3>Programming environment</h3>
<p>I think QBasic is still a pretty good baseline when it comes to introductory programming environments. I want to update that experience with Wee:</p>
<ul>
<li>A familar UI/UX that avoids information overload, invites exploration through simplicity, and lacks ceremony.</li>
<li>A great on-boarding experience with instant gratification, even for people that do not read the fine manual.</li>
<li>A contextual help system, worded in a beginner friendly way, with references to other learning materials.</li>
<li>Minimal context switching.</li>
<li>Immediate feedback on errors, with <a href="http://elm-lang.org/blog/compiler-errors-for-humans">error messages for humans</a>.</li>
<li>A capable debugger.</li>
<li>Code navigation, completion and refactoring facilities.</li>
<li>Collaborative editing.</li>
<li>Source control management.</li>
<li>Simple mechanism to share programs and libraries.</li>
</ul>
<p>Again, much of this is fuzzy. I'll figure out the details once I get there. This includes the choice of platform. Currently, I gravitate towards the browser, for multiple reasons. I can more easily distribute fixes and additions to the platform and learning materials. It is more conductive to community aspects, including sharing. It's (mostly) cross-platform. There are of course also quite a few downsides. Not everyone is connected to the internet all the time. Getting a complex application like this to work across browsers is a bit of a nightmare. Finally, as a user, I might not want to leave my code with a 3rd party.</p>
<p>An alternative to the browser would be a <a href="https://code.visualstudio.com/">Visual Studio Code</a> plugin. I would have to re-invent less, and the user would work with a professional tool. But I also could not control the experience fully, and community features are harder to integrate.</p>
<h3>Programming language</h3>
<p>QBasic was far from perfect, but I think some of its features and limitations were actually conductive to me learning to program. I already have a pretty good idea of what Wee's language design will look like. It still needs to pass the Michael smell test, so I post-pone going into details until the next article. I can however present some basic elements:</p>
<ul>
<li>Statically typed structured programming language.</li>
<li>Support for modules that can be used as libraries.</li>
<li>Namespaces, one per module.</li>
<li>Automatic memory management (but without garbage collection).</li>
<li>(Almost) Everything is a value type.
<ul>
<li><code>unit</code>.</li>
<li>Primitive types (boolean, int, float, string, functions).</li>
<li>Lists and dictionaries.</li>
<li>Non-recursive records.</li>
<li>Reference type that can not escape into parent scopes (function arguments, local variable references).</li>
</ul>
</li>
<li>Default value initialization.</li>
<li>List, dictionary and record literals.</li>
<li>No implicit narrowing/widening/conversions.</li>
<li>Standard set of arithmetic, logical and relational operators, with standard precedence.</li>
<li>Assignment operator deep copies (including arrays).</li>
<li>Control flow constructs (if, for (each), while, match).</li>
<li>Functions
<ul>
<li>Immutable pass-by-reference by default.</li>
<li>Pass-by-value and mutability argument modifiers.</li>
<li>Can return any type or <code>unit</code>.</li>
<li>Named arguments.</li>
<li>Overloading on argument types.</li>
</ul>
</li>
<li>No aliasing except for reference function arguments.</li>
<li>No <code>null</code>.</li>
<li>Error handling TBD (possibly unchecked exceptions).</li>
<li>Standard library (strings, i/o, graphics, audio).</li>
</ul>
<p>While you can already start the bike-shedding, you may want to wait for a future article where I'll dive into the details. I have not decided on the runtime yet. I think for V1, I'll write a byte code interpreter for which multiple implementations can be written. Since I'm likely going to target the web first, I might look into WebAssembly as an alternative target. Ideally, Wee could be embedded anywhere.</p>
<h3>Community</h3>
<p>The final ingredient is a thriving community. In the old days, various scathered websites and forums as well as IRC channels served as community hubs. For Wee, I'd like this to be a bit more centralized, integrating it with the learning materials and the programming environment:</p>
<ul>
<li>Users can create an account and get access to the below features.</li>
<li>Server-side storage and versioning of programs.</li>
<li>Sharing and remixing of programs.</li>
<li>Collaborative editing.</li>
<li>Forums.</li>
<li>Let users publish articles, dev logs, etc. (could be part of forums).</li>
<li>Comment sections for programs and learning materials.</li>
</ul>
<p>Funny as it may sound, I feel the least confident in building this part. There is also a massive can of worms to be opened on the legal side of things, especially when kids are involved.</p>
<h2>Next up</h2>
<p>Now that I have an idea of what I want to achieve, I can start with the actual work. The next step is finishing the design of the language. Once that's complete, I'm going to share it here so we can all have a nice round of bike-shedding.</p>
<p>As always, I'm happy to hear from you on <a href="https://twitter.com/badlogicgames/status/1020456081640869888">Twitter</a> and appreciate corrections to my terrible writing in form of a <a href="https://github.com/badlogic/marioslab-site/blob/master/site/posts/wee/history-lessons/index.bt.html">pull request</a>. If you are interested in helping out with Wee, or want to give feedback that does not fit into a tweet storm, you can reach me via <a href="mailto:mario@badlogicgames.com">email.</a></p>
{{include "../../../_templates/post_footer.bt.html"}}