Showing posts with label dstr. Show all posts
Showing posts with label dstr. Show all posts

2021-07-13

Dr.Barbara Liskov`the software crisis averted with modularity based on data abstraction

21.6.10:  7.12: news.adda/lang/

Dr.Barbara Liskov`the software crisis averted with modularity based on data abstraction:

. the key to programming in the large 

is modularity based on data abstraction

and that was not obvious before her work,

but now her work is mainstream, so it now seems obvious.

2013-12-09

type.tags for aggregates

10.17: adda/oop/type.tags for aggregates:
[12.8: intro:
. if we were to have a generic aggregate,
what would its type.tag look like?
(the purpose of a generic aggregate
is having something that packs objects together,
and can be easily created and traversed .

2013-12-07

target-owning vs aliasing pointers

12.7: summary:
. this explores types of pointers
needed for adda's space-efficient style of oop,
and support of safe pointers .

2013-03-09

managing self-modifying code

1.31: adda/managing self-modifying code:
(inspired by python unpickle vulnerability)
. the safe pickle is built by the system .
. it can be compared to the decompile,
how is it extensible? that is to ask
how are objects built in the first place?

2013-01-31

virtual C

1.11: adda/dstr/safe pointers/virtual C:
. adda's pointers can feature arithmetic exactly like c;
yet it can still remain safe because
the addresses are not absolute;
the pointers are actually actually just offsets .

2012-11-17

arrays are records are tuples

8.19: adda/dstr/array and record syntax as modes of access:
if ( x.y ) and ( x#y ) are both valid
regardless of whether x is defined as an array or record;
this ignores an idiom where
arrays represent lists of items,
while records represent items with named parts;
. well, my way lets you do it both ways,
but it should be admitted that the writer's freedom
is inevitably the reader's headache .

adda/dstr/array vs record precedence:
. if you have x#y.z
does it mean x#(y.z) or x#(y).z ?
my first impression is that y.z is the tightest binding;
ie, it means x#(y.z);
also, (x#(y) = x#y) only when y is an atom,
and you could argue that y.z is not an atom:
it's a sort of address-addition operation; [11.16:
but, of course, that would be a stretch!
it's more intuitively seen as a style of atom naming .
. finally, consider how functions and arrays
should be similar;
( f x.y ) should be seen as a variant of
( a# x.y ); therefore, the parsing is ( a#(x.y) ). ]
. x.y#z is unambiguous: x has component y,
which is an array taking parameter z;
ie, it's parsed as ( (x.y)#z ).

8.20: adda/dstr/arrays are a species of record.type:
. an array is generating a list of component names,
and then declaring them all to be the same type .
. records are a generalization of this,
where a generated list of components
can have a variety of types .
. in fact, fully-oop arrays are actually records;
because, they often have a variety of types .
. arrays are parameterized types,
and records can be parameterized too
. here's a syntax for allowing a record
to describe parts of itself the way arrays do:
reca(n.int).type: ( a.t1, b.t2, #(0..n).t3 );
x.reca(3)
-- now x = (a:..., b:..., 0:..., 1:..., 2:..., 3:... ).

2012-11-16

Ada's core attributes

8.20: web.adda/ada/predefined attributes:
. what are Ada's reserved attributes?
here they are renamed for clarification
(these are just the most basic ones;
there are many more for floats, etc):
type'base  -- the unconstrained subtype;
enum in type'first .. type'last,
A'range(N) =  A'first(n) .. A'last(n) -- array's nth dimension;
type'value(enumcode number or image string);
type'image(value) is a string;
type'code(value) -- the integer representing the value (aka pos);
type'codebitsize -- obj's fixed size (when packed);
rec.component'codebitoffset;
type'imagesize -- #characters(for largest expression);
type'++(value) -- successor;
type'--(value) -- predecessor .

benefits of unique context operator

8.19: adda/lexicon/benefits of unique context operator:
[8.20: intro
. in 2012.4, this "(::) was presented as a context operator
but it didn't give a specific reason:
"( confusing having syntax"(type`value)
when (`) already has a specific meaning: (x`f); ). ]
. even more than confusing,
it's name`space limiting:
if you have the syntax ( type`attribute ),
along with ( type`method ),
then the type authors are limited in
what they can name their methods
because it could clash with type attributes; [8.20:
eg, for enums there is an attribute named first;
eg, for bible.type: {last, first},
bible`first = last; -- this type's first value is "(last);
but if the context operation uses the same symbol,
then ( bible`first ) could mean either
the first value of the bible enumeration,
or the bible value named "(first).
. by having a separate context operator (eg, ::),
we can say ( bible`first = bible::last ).]

8.19: 11.15 .. 16: review the syntax:

obj.type(select a variant of this type)
-- declares obj to be of this type;
obj`= value -- object initialization;
type::value -- fully-qualified enumeration value;

obj.component -- public ivar;
type::.component -- public ivar's default initial value;
obj#(component expression) -- public ivar;
type::#(expr) -- public ivar's default initial value;

type.component -- public class var;
type#(component expression) -- public class var;

obj`body/local -- private ivars;
type`body/local -- private class vars;

obj`message(...);  -- instance message call;
obj`message`body -- instance message's body;
type::`message`body -- instance message's body;
type::`message -- instance message uninstantiated
( practically the same as type::`message`body );
type`attribute -- class message call;
function(obj) -- call to function of instance
(may or may not belong to an obj's type's interface);
type::subprogram -- full-qualified subprogram call;

type::function`body -- access function's body;
type`body/subprogram -- private class subprogram call;
obj`body/subprogram -- private instance subprogram call;
obj(expr) -- obj callable? apply('obj, expr);
function obj -- call with this obj

. notice there are separate namespaces for
{ value and function names
, .components
, `messages }; because,
components are found only after a dot,
and messages only after a backquote;
whereas, the namespace for value
is shared by that for functions;
otherwise, the parser would have problems:
both values and functions start with a name
but only functions expect the next lexel
to be the argument of that function:
# function x -> apply function to x;
# value x -> syntax error
( unless x is a binary operator
or value is numeric and x is a dimension ).
. therefore, for each unqualified name
it must be typed unambiguously as
either a function or a value, not both .

11.15: mis:
"(review the syntax:
type::value -- fully-qualified instance value
type::`message -- access message's body
type::function(...) -- access function's body
obj`body/local -- private ivars;
type`body/local -- private class vars;
type`attribute -- class message call;
...
) . sometimes it is using (type::x) to mean eval x,
but other times don't eval?
and then this:
type`body/local -- private class vars;
?
. the body of the function is within
the body of the type:
type`body/function
and the way to refer to the function uneval'd,
is to ask for the function's body:
type`body/function`body .
. but if the function is also visible from the interface
we could also write:
type::function`body .

11.16: clarification:
"( review the syntax:
type.component -- public class var;
obj.component -- public ivar;
type::.component -- public ivar's default initial value
) . an interface definition has syntax for
both class and instance public vars,
and these are accessed similarly,
being dotted with their respective objects:
obj.component -- public instance var;
type.component -- public class var .
. if you hadn't defined an instance yet,
and still wanted to refer to an instance's component,
that would be done with the type's context operator:
type::.component;
and, since there was no instance involved,
the only meaning it could logically have
is being the component's default initial value .

2012-10-06

safe pointer details consider concurrency

8.4: adda/dstr/safe pointers/
space-efficiently pointing to both static type info and obj:
. we need to point at the symbol table index
not the obj itself, because,
only the symbol table index has the type tag
in the case when the type is static .
. as for space-efficiently doing this
at first I was worried about the
huge length of full paths
(process#, act'rec#, symbol table index);
but, [8.3:
just like urls have versions,
{fullpath(absolute), filename(relative addressing)},
pointers too should be available as
both long and short versions:
the full path is
(process#, act'rec#, symbol table index)
but if you are in the same act'rec,
you'd use a shorter version of the pointer:]

. if you are in the same activation record,
then the pointer is a mere 16bits:
(we assume here that a pointer needs a 3bit type tag,
and that the max# of symbols is 2^13 .

[8.21: 3bits?
. locally we need no tag at all,
because we just assume everthing is local;
otherwise, if it's a general pointer,
it will have the general type.tag arrangement
which usually means a static supertype,
and a dynamic on-board subtype,
which in the case of pointer needs 2 bits
for these 4 cases:
( internet address,
, local to system
, local to process
, local to act'rec ) .]

8.26: processID irrelevant:
. do we need to track the processID?
. any obj' we'd want to point at
is located within some act'rec;
so, isn't the act'recID enough?
8.28: 8.26:
. the act'rec obj' could have a subtype.tag
so that the same act'rec supertype
could represent all sorts of objects
like subprograms, coprograms, type mgt obj's, etc .
10.6:
. the main place to for finding out
how the current act'rec is related to a process
is by asking the task mgt who is
using this act'rec ID as a node index
into a tree of processes and their act'recs .

8.28: processID irrelevant despite pointers:
. when using pointers, the process ID matters;
because, we can give a pointer for assignments?
but that's not the problem of our pointer:
it has only to locate a target,
and it's up to exec to do the right thing .
. any time an assignment takes place
the target must be either a global obj,
(these are essentially act'recs because
they're made thread-safe by msg-taking functions)
or the owner had to have ordered the assignment
(that means either the owner is suspended for a subroutine
or the target is locked for a promised assign by an async).

8.26: processID fundamental?:
. we'd like to keep pointer sizes small,
so if we could know that an act'rec would
stay local to a particular process,
then we could possibly divide the number of act'recs
by the number of processes .
. we could say that each process has its own heap;
and, just as each act'rec has few symbol nodes,
a process has few act'recs,
so this is another form of subheap
the process subheap vs an actrec subheap .
8.28:
. unfortunately, given the use of recursion,
the number of act'recs per process can still be
arbitrarily large, so this would do nothing for
the size of an act'rec ID .
. the situation where process is fundamental
would be in systems with unprotected vars;
because, then process is a unit of thread safety:
anything can access any var within the process,
and then the process has only one thread .
. what we could have instead,
is a system where encapsulation and locks
are the basis of thread-safe computing:
. anything accessing a var must instead
ask that var's type mgt to do the accessing,
and then the type mgt has only one thread .
. a var lock gives sole ownership of a var
to another thread .
. in that sort of system,
a process is nothing more than an act'rec
that is run concurrently vs sequentially .

2012-08-23

folder naming like internet subdomains

7.12: adda/dstr/folder naming like internet subdomains:

. in the naming system for subsystems,
the naming of a folder can have a special meaning
because the folder represents
something like a website;
so, just as the top domains
(.com, .org, .net ...)
indicate a website's role type,
these folder types ( .pkg, .type )
can indicate top level domains in our
network of modules .
[8.21:
. later that system would change so that
(.pkg) was the implicit datatype
of any untyped folder
if it was within adda's path .]

. a module can be divided into several folders
by using subdomains; eg,
given these top-level modules:
( int.num.type
, real.num.type )
we can see that both int and real
belong to the same datatype, num;
so, when importing num.type
we're reaching all the folders named *.num.type .

8.8: on the other hand:

atoms equivalent to tuple singletons

7.7: adda/syntax/atoms equivalent to tuple singletons:
. does a function's arg need a parenthetical?
ie, can we say( f x ), instead of( f(x) )?
if (f x) can be the same as f(x),
then all atoms (such as x)
can be seen as singleton tuples (ie, x=(x) );
so, then there's no need for adda to copy
Python's singleton tuple syntax: ( (x,) ).

2012-05-31

combining records and arrays

5.4: adda/dstr/combining records and arrays:
5.31: summary:
. records and arrays in the Python language are seen as
2 different operations even within the same object;
and, that is a feature that can't be emulated
if records and arrays are seen as
2 methods for accessing the same components;
eg, you couldn't have separate components for
agg.field and agg# field .
. we can use arrays as an extension of records,
so if the array has no such item agg# field,
then it checks for an agg.field;
and conversely, if there's no agg.field,
then it checks agg# field
-- always giving priority to the accessor in use,
so that there can exist both agg.field and agg# field .
. if there exists an agg.field
and we'd like to create an agg# field,
we can do so by making the assignment: agg#(field)`= ... .
. if we haven't yet made that assignment,
then reading agg#field will default to returning agg.field .

. records and array notation have been seen as interchangeable;
but, Python's ability to provide both
dictionaries and record fields,
has me wondering if I can do that,
and still use them interchangeably .
[5.30:
. an array is simply a record with an additional constraint:
all the components or fields are the same type;
however, with oop, arrays can be varying type
as long as all fields have a common supertype .
. conversely, record field selection can be computed
just like arrays use indexing for selection
if there is some function that returns symbols,
(if the symbol isn't a field then there's a range exception). ]
. the record notation has been for
any time one wants to use a literal field name
whereas the array syntax has been for
computing the field name(ie, with a non-literal)
but of course computing included the use of literals .

. the syntax for doing both would be .< .( ...), #().t >
(that's a class definition with nothing but
record and array accessors).

. we want all adda symbols to be typed,
so, to allow our arrays to be like python's dictionaries,
we need a type called "(any) (same as Object)
to indicate an escape from a specific type .
. then our pythonic symbols are typed .tall
(pointer To ALL)
and that type includes the pythonic array access:
#(hashable).any .

. if the domain is floats
then interchanging {rec, array} is confusing
because the dot is used as both a field selector
and the float's fraction selector;
eg, does x.1.2 mean x#(1.2), or x#(1, 2) .
. so then, another rule of record type is that the
domain type must be a symbol, not any hashable ?
. it would be better if records and arrays were
not interchangable, except that
if you would like to compute a record's name,
you can do that with array notation
but if you use a record's field selector (x.field)
then you mean x has been declared a record
... uh,
if a symbol can be both record and array
then record names cannot be computed,
because, that requires the use of array notation,
but then the reader will be getting confused:
are you referring to your symbol's record fields
or to its array components ?!
[5.30: nevertheless, it could work because,
we are saying that the array and record
are accessing the same fields;
ok, but,
didn't you want your record's fields to be typed?
. we could say that by defining record fields,
we're reserving those fields to be a particular type,
so then if the array assigns them some other type,
that raises an exception .]

another way record's can be like arrays is you say ...enum,
.( .. .trafficlight: .t ) as shorthand for
.( black, red, yellow, green: .t )ie,
in a record declaration,
.. can be followed by an enum type name
you can also list integer ranges?
. uh,
as part of keeping the language simple,
if we want to use range notation,
we should declare it to be an array .
5.30:
. if we didn't declare the domain to be type .type,
then types could still be shorthand for sets of values,
so we could give assignments like this:
A#( .trafficlight)`= {0; 1; 2; 3}
as shorthand for
A#{black; red; yellow; green}`=  {0; 1; 2; 3} .

2012-04-30

{parameterized, variant} records

adda/type/{parameterized, variant} records:

4.15: adda/dstr/variant vs parameterized records:
. a parameterized type is not the same thing
as a variant record;
the parameter of a parameterized record type
works like a function, representing a set of subtypes;
eg, it might determine the max length of an array,
or the type of a list's items .
. the variant record, on the other hand
-- which is what is expressed by
.(x? #1: v.t1; #2: v.t2; #3: v.t3 #.)  --
is not requiring a parameter to express its discriminant;
the discriminant can be changed dynamically,
and must be changed in order to mirror
any changes in the way the variant is used .
. conversely, if the discriminant is changed,
then the current variant it represented must be erased,
as the new variant is initialized .

an array is not a function!

adda/dstr/syntax/is an array a set or a list?:
4.29: summary:
. an array should be seen as a special case of record,
where the component types are all the same .
. while a component selection has the same functionality
as applying an argument to a function,
and a function can be implemented with an array,
only a read-only array is equivalent to a function .
. the following is what I wrote on the way to
clearing this up in my mind .

when to type an object as pointer

4.1: 4.2: adda/dstr/pointer/when to type object as pointer:
implicit conversions:
. in designing syntax, it's been assumed that explicit is good,
hence we encourage the explicit use of pointers;
but that idea is at tension with trying to avoid
unnecessary implementation details .

2011-06-30

enum.typed record domains

6.20: adda/dstr/enum.typed record domains:
. in classic lang's like c,
arrays had very different properties
than records .
. in a polymorphic languages however,
records can be more like arrays;
because, although their components vary in type,
the various types often have the same base type;
eg, all types can print their value, etc .
. and any time a record has at least
2 components of the same type,
it could make sense to specify components with
an expression rather than a literal name,
just as is done for arrays .
today's issue is this:
. in trying to unify the concepts of
arrays and records,
there needs to be a way for records to
express their component names as being
some enumeration-type's subrange;
and then, conversely,
any time a record r declares its components' names,
that should induce an enumeration type:
r`domain
-- I'm using the term "(domain) because
arrays and records are like functions;
and, functions in math are known to
input values from a domain type
and output values from a codomain type;
ie, f: domain -> codomain;
or, in adda:
function f(x.domain).codomain,
array A#(domain).codomain
record R.(r1.co1, r2.co2, r3.co3);
R`domain = {r1, r2, r3};
R`codomain = {co1.type, co2.type, co3.type},

. assume there's the enumeration
e.type: {r1, r2, r3};
how should a record say its domain = e ?

R.(r1.co1, r2.co2, r3.co3);
R`domain`= e ?
[6.23: yes,
that lets the record be defined in the usual way,
and then also say that the domain is
not a new declaration,
but rather a reuse of some existing enum.type .]

mis:
. one thing that wouldn't work is this:
R#(e).{co1.type, co2.type, co3.type} ?
-- that would instead be saying
R is an array of enum;
and each var's possible values
are in the set:
{co1.type, co2.type, co3.type}.
--. this could be useful for when
writing programs that write programs:
many of the values they're concerned about
are parts of languages, including types .

2011-02-28

literals with type-defined syntax

2.28: adda/dstr/literals with type-defined syntax:
. one way to allow custom syntax
-- elegantly, without a bolt-on --
is to say that types can define
their own literal reader;
ie, a mini' type-specific compiler .
. this allows the grammar of literals to be
something other than a composite of native types,
defined instead as whatever is accepted
by the type's reader .
. during the adda.compiler's first pass,
it sorts out what's adda code
from what is either a string literal,
or a type-specific literal
having a reader-defined grammar .
. in subsequent passes,
it then uses the appropriate reader
to complete the translation of literals .

. a custom reader is not a security threat;
because while it is returning adda binary code,
that code itself is not runnable;
it must still be translated by trusted app's .

. if a type defines more than one reader,
then not mentioning a reader simply calls the default .
it can also be explicit in the usual way:
eg, .t`yet-another-reader
is a reader belonging to type t .

. the pattern:  x.anytype = "(...)
results in calling anytype's default reader
-- just as with: x.string = "(string's literal);
( notice .string's default reader
doesn't have to be trivial;
in c.lang, the reader treats "(\)
as an escape character;
eg, \n -> newline, \t -> tab, ... ).

. to help adda readily identify
all the type-defined syntax readers,
they should all return the same special type,
say, .addb (adda binary),
so then in type t's interface,
any functions of type .addb
will be registered as readers for type t;
eg,
( read(x.$).addb
, another-style(x.$).addb, ...)

. a quote lexeme -- '{}, '[], '() --
means do a read (translate text to adda code)
but don't eval,
whereas, a double-quote means don't even read:
it is to be considered { .string, .$ };
so, in the case of readers,
their parameter must be a double-quoted enclosure,
or some expression that returns .string;
otherwise, it's eval'd as adda code
before being given to the reader
and then it might not even be the expected .string type .

. places where literals are encountered:
# static typing:
. the var's are declared to have a particular type;
here the type is obvious;
so the type qualifier is not needed;
eg,
x.anytype`= "(this is greek to adda)
-- that invokes .anytype's default reader;
x.string`= c-style"(string's literal\n)
-- .string's c-style reader is invoked .
# dynamic typing:
-- the type is discovered at run-time --
. adda can't find a reader at compile time
unless the reader's type is specified:
eg,
say .tall can point to all types:
xc.tall`= .string`c-style"(featuring escapes\n) .
-- now xc can point at a .string at compile`time .
. if an undeclared function is given:
eg,
g.tall`= f "(something like x );
then the work is left to the run-time exec:
it sees if the current object assigned to g
does have a type that includes
the function type: f(x.$).*
(where * can be any type);
if so, then g gets whatever type obj'
that f returns .

. string literals can have the same problem as comments:
it's easy to lose the boundaries of multi-line constructs;
and, when that happens,
the code can act strangely because
the compiler thought half the code
was a comment or string;
 conversely,
if the comment or string accidently contains
the string delimiter,
some of the comment or string will be
compiled as if were code;
and if that succeeds,
the results will certainly be unintended!
. to assist the human reader,
there could be a redundant enclosure syntax:
if a string can't fit on one line,
the enclosure boundaries should be
on their own separate lines;
"(
example with
2 lines .
);
. if the text includes quote enclosures on their own line,
then the text could go in its own file:
x`= "( [!]myliteral.txt )
-- when {adda, adde} sees [ ! ] as one word,
then what follows is a command for generating text .
. a .txt file would be taken as literal text;
whereas an .adda file would be eval'd to an object
that would then be printed if not alread .string .

menucode

2.4: adda/dstr/menucode:
. menucode is a variation of the index-style pointer
. it's a finite enumeration that is then mapped to
pointers or indexes .
2.14:
. what pointers and indexes have in common is
being addresses of a memory implementation .
. this is in contrast to opcodes and menucodes
which are specifying what you want to address
rather than where its address is .

2011-01-31

byte-sized pointers

1.18: adda/dstr/byte-sized pointers:
. keep pointers small by seeing large lists
as subtrees;
one of the node types of a tree is
"(this subtree root declares a new subheap:
it includes access to a 256-size array of tree node).
. how does that scale? it does because
root of huge subtree had its own subheap
where all terminal nodes were tagged as
(this is a sys'ptr [32bit c pointer]
-- not a pair of byte-sized tree ptrs [byte'ptr])
[1.31:
. as the tree progresses downward with fanout,
it eventually consumes 256 nodes;
any tree-building further down
must be done from a new-subheap node`type
(ie, a node whose purpose is to declare that
the following subtree is based in a new subheap).
. lets say that a tree uses
2 subheaps worth of nodes;
then if the tree is balanced,
it can be built with only 2 new-subheap nodes
by putting the left and right subtrees
each in their own subheap;
if the tree is not balanced,
then space efficiency requires that subheaps be shareable,
so that many smaller subtrees can be
impl'd on the same subheap .
. to make best use of space,
the use of new-subheap node`types
must be minimized because they are overhead .]

. when having a ptr to subtree,
it needs a record:
sys`ptr to the subheap,
byte'ptr to where in the subheap subtree is rooted .

. when building trees from text, [1.31:
or when trees can be modified? ..]
then we need speed more than mem'
whereas figuring out how to pack for byte-ptr's
would be very time-consuming;
so, then these sort of trees should be word-sized .
. both sizes are distinguished from sys'pointers
with the term "(local ptr):
whereas a system pointer is logically a machine address
(practically an index into a process module),
a local ptr is always an index into the current subheap .
1.31:
. our tree node has less than 16 variants,
so the tag can fit in 4bits (a half-byte, nibble,
-- addressing a byte to extract the {hi,lo} 4bits );
the node's data will be sizes larger than a nibble,
and the tag should be at the beginning
to tell us what the rest of the node looks like;
so, in order to accommodate mem'allignment requirements,
without wasteful padding after the tag,
the subheap will have 2 forks
so that tags and data can each be
in their own arrays .

. a subheap supporting byte-ptr's
can hold only 256 nodes,
in turn needing a 128-byte chunk for the 256 * 4bit tags .
. these nodes are word sized:
#branch: (tag, left.byte-ptr, right.byte-ptr);
#leaf: (tag, symbol.word-ptr).

. subheaps are composed of chunks;
so, if a subheap is not enirely used,
it's not entirely alloc'd either .
. both byte-ptr and word-ptr subheaps
can use the same size chunks, 128 bytes,
to match the minimum chunk needed for tags .
. therefore 256*2byte data nodes = 128*2*2 = 4 chunks .
. if less than half of nodes are used,
then it can save on 2 chunks of 128 bytes .

. finally, a byte-ptr subheap needs to be
an array of 5 sys'pointers for access to the
1 tag chunk + 4 data chunks,
plus
whatever else is needed for variant tagging,
or part of an efficiency hack .

. if a large structure has a known minimum at init' time,
then it can be given a superchunk,
which is some arbitrary multiple of contiguous chunks .

2010-12-30

vector literals syntax

12.28: adda/dstr/vector literals:
. as with the declare's syntax
the opening angle brackets of the
vector literal formed with angle brackets
could be distinguished from less-than
by following it with a dot .
. ascii tokens are useful even with unicode
because, keyboard's can quickly dish up ascii .
. the parser converts ascii to unicode's:
less-than & dot -> opening vector bracket (U+27E8)
--. the closing pair is: U+27E9 .
related trivia:
Miscellaneous Mathematical Symbols:
U+27C0 ... U+27EF, U+2980 ... U+29FF .
'Miscellaneous Technical' 2300 ... 23FF:
(has a left-pointing angle bracket U+2329) .
--. ascii codes for {math relations, brackets}:
U+003C Less-than sign
U+003E Greater-than sign .
. unicodes that mac os x supports .