1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser
44 SysUAF API

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

UCL
59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1
66 UCL Expressions, Part 2: Support code
67 UCL Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 UCL Expressions, part 4: Evaluation
70 PROCESS_SCAN

Glossary/Index


Download sources
Download binaries

UCL Expressions, Part 1

In this article, we will begin a discussion of expressions in UCL. UCL and DCL expressions should be 100% compatible, so if you are already familiar with DCL, you can skip to the next article. Otherwise, we will cover the basics here.

UCL Expression Primer
Expressions are used in assigning values to symbols, in IF statements, and in function parameters.

Values
Expressions consist of operators and values. Values can be literals, symbols, or functions. A numeric literal is an integer numeric value and would be something like:
32767
whereas a string literal is delimited by quotes, containing any characters, and would look something like:
"This is text"
We've discussed symbols before. In expressions, the value of the symbol is used as if it were a literal value. Note that no substitution is required to use symbols in expressions unless, of course, you wish to have a level of indirection. In fact, unless you want indirection, you probably do not want to use substitution. Consider this scenario: You want to compare text stored in symbol A with another value. If you use the code:
A .EQS. "Text"
then the contents of A will be compared to "Text". The quotes are not included in the comparison since they are simply syntax to indicate to the UCL parser that the value is text instead of a symbol called Text. If symbol A contains "Text", then the comparison will be true since they are equal. But now let's consider if we use substitution like so:
'A .EQS. "Text"
In this case, A is substituted with it's value before the expression is evaluated. Thus, the expression parser would see the following:
Text .EQS. "Text"
Because the first "Text" isn't in quotes, it is assumed to be a symbol named "Text". The value of that symbol would be used for the comparison or - worse yet - there is no symbol named Text and the expression will result in an undefined symbol error.

Numeric literals can also be specified in hexadecimal (base 16), by prefixing the value with "%X". For instance:
-%0FF
would be equal to -255.

Functions
Another type of value is the function. UCL functions are called "lexical functions" and are always prefixed with "F$". We will discuss these functions in a later article, but let us used the F$EDIT function as an example. Functions take parameters, specified within parentheses and delimited by commas. Different functions take different numbers of parameters, with different meanings. F$EDIT, for instance, takes two parameters and a reference to it would look something like:
F$EDIT(P1,P2)
where P1 and P2 are values that the function uses to do some sort of calculation and then return the result of that calculation. That result is a value which the expression parser can use as if it were a symbol reference or literal. For example:
A .EQS. F$EDIT(B,"UPCASE")
Each parameter is an expression in itself.

Operators
An operator is something that takes two values and does something with them. There are two types of operators: arithmetic and logical. Arithmetic operators are things like addition and multiplication. Logical operators perform comparisons between values, such as Equal or Greater Than. Because an expression can contain multiple values and operators, some means of determining which order the operations occur must be defined. In Algebra, for instance, the following two equations result in different values:
1+2*3
3*1+2
But the following two result in the same value:
1+2*3
2*3+1
The reason is because, the rules of alegbra say that multiplication has a higher precedence than addition, so the multiplication is always done first. Thus, we cannot simply process operators and operands from left to right and have the result be algebraically correct. Here are the arithmetical operators and their precedences:

OperatorPrecedenceDescription
-7Unary minus
+7Unary plus
*6Multiplication
/6Division
+5Addition
-5Subtraction
The higher precedence operators are evaluated before lower precedence operators. If the operators have the same precedence, they are evaluated left to right. Of special note are the unary operators. A unary minus is used to negate a numeric value. For instance:
-A
If symbol A has a value of "5", the above evaluates to -5 (negative five). Unary plus essentially does nothing since it leaves the value undefined. For instance, +5 is the same as 5 and +-5 is the same as -5. You may have noticed that unary operators do not work like the other operators - they affect a single value instead of two values. The other operators could be called binary operators since they operate on two values. The unary operators must occur on the left side of the value to which they apply.

Here are the logical operators and their precedence:
OperatorPrecedenceDescription
.EQ.4Numeric values equal
.EQS.4String values equal
.NE.4Numeric values not equal
.NES.4String values not equal
.GT.4Numeric value greater than
.GTS.4String value greater than
.GE.4Numeric value greater than or equal to
.GES.4String value greater than or equal to
.LT.4Numeric value less than
.LTS.4String value less than
.LE.4Number value less than or equal to
.LES.4String value less than or equal to
.NOT.3Logical NOT
.AND.2Logical AND
.OR.1Logical OR
Logical operations work on boolean values: true and false. We will discuss what boolean values look like later in the article. The comparison operators (all with precedence 4) are used to compare two values. There are two types of each comparison: numeric and string. Consider the following expression:
A .EQS. B
The result of this operation is true if the contents of symbols A and B exactly match, and false if they do not. Likewise:
A .LT. B
compares A and B, as numbers, and returns true if A is less than B.

The last three operators perform operations on boolean values. The following tables express the results of these operators.
.AND.
OperationResult
false .AND. falsefalse
false .AND. truefalse
true .AND. falsefalse
true .AND. truetrue

.OR.
OperationResult
false .OR. falsefalse
false .OR. truetrue
true .OR. falsetrue
true .OR. truetrue

The .NOT. operator is a unary operator that simply inverts the boolean value, like so:
OperationResult
.NOT. falsetrue
.NOT. truefalse

Data types
Above we discussed three different types of data: numeric, string, and boolean. However, UCL doesn't have strict data typing, so it is important to understand how values are interpreted when certain operators are used with them. For boolean values, any number that is odd (1,3,5, etc) is considered "true" and any other number is considered "false". Any string that begins with an uppercase or lowercase T or Y is considered "true" and any other case is considered "false". Boolean results are always "1" for true and "0" for false.

If a symbol contains a numeric value, that value is used as-is for numeric operators. But if the symbol contains something that is not numeric, it is treated as the number 0 for numeric operations. Note that UCL only supports whole (integer) values. Signs (plus or minus) are allowed, but trying to include a fractional point (a decimal point) means it will be interpreted as a string (thus, 0) for numeric operations. Note that any string that begins with an uppercase or lowercase T or Y is considered to have a numeric value of 1.

String operations, of course, work with any type of value. Booleans are treated as "0" or "1", and numeric values are nothing more than a string of decimal digits. However, it is important to note that the type of operator used with a numeric value can affect the result of the operation. Consider the following two cases:
"0100" .LTS. "10"
"0100" .LT. "10"
Because the first operation is a string comparison, the values are handled as a string of characters and the result is true because a string beginning with "0" sorts before a string beginning with "1" and therefore "0100" is less than "10". However, in the second example the result is false because the values are interpreted as numbers instead of strings of characters, and 100 is not less than 10. Also consider the following comparison:
"A" .LT. "10"
Although "A" sorts after "1" as a string, because .LT. operates on numeric values, and "A" is not numeric, the "A" is interpreted as a 0, which is less than 10 so the result of the expression is true.

Coercing precedence
Parentheses can be used to alter the order of evaluation to be different than that which results from precedence. Consider the following two expressions:
1+2*3
(1+2)*3
Because of order or precedence, the first expression evaluates to 7. In the second expression, the result is 9 because the contents between the parentheses are evaluated as if it had the highest precedence.

Differences between UCL and DCL
Syntatically, UCL is backwards compatible with DCL. However, there are some differences in behavior.

  • Internally, integers are 32-bits in DCL, but 64-bits in UCL.
  • DCL has rudimentary data typing: symbols are typed as either numeric or string. UCL has no typing. Values are treated as integers when integers are required and as strings otherwise. Thus, in UCL, the lexical function F$TYPE will indicate an integer if the symbol is a valid integer and as a string otherwise, reguardless of the means of assigning the value. Likewise the UCL F$STRING lexical function does nothing but return the current value. Note that function parameters are also typeless, although errors can occur if the function expects a valid number but doesn't receive one.
  • DCL doesn't allow spaces between the periods (.) and the rest of the operator name, but UCL does (eg . EQ .). This is not purposeful - it is simply a consequence of the parsing code and it would have taken more work to restrict it like DCL. In this respect, UCL is backwards compatible, but DCL is not forward compatible. That is to say, UCL will handle all valid DCL syntax although DCL won't handle all valid UCL syntax. Incidentally, explaining why the parser works this way would require an in-depth discussion of its operation, which we simply won't address here.

Syntax specification
Here is the Backus-Naur form (BNF) definition of UCL expression syntax:

expression ::= subexpression | subexpression operator expression

subexpression ::= function | value | unary expression | value operator expression | ( expression )

value ::= symbol | literal

unary ::= - | + | .NOT.

symbol ::= letter | letter alphanum

letter ::= A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z

alphanum ::= letter alphanum | digit alphanum

digit ::= 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0

operator ::= + | - | / | * | logical | comparison

logical ::= .AND. | .OR.

comparison ::= numeric-comparison | string-comparison

numeric-comparison ::= .GE. | .LE. | .GT. | .LT. | .NE. | .EQ.

string-comparison ::= .GES. | .LES. | .GTS. | .LTS. | .NES. | .EQS.

literal ::= " anychar " | number

number ::= decimal | hexadecimal

decimal ::= digit | digit decimal

hexadecimal ::= %X hexdigits

hexdigits ::= hexdigit | hexdigit hexdigits

hexdigit ::= digit | A | B | C | D | E | F

function ::= F$ letter alphanum ( parameters )

parameters ::= expression | expression , parameters

Now that we've described the basics of UCL expressions, in the next article, we will examine the code which parses and evaluates them.

 

Copyright © 2019 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.