CO538 Anonymous Questions and Answers |
This page lists the various questions and answers. To submit a question, use the anonymous questions page. You may find the keyword index and/or top-level index useful for locating past questions and answers.
We have taken the liberty of making some minor typographical corrections to some of the questions as originally put. Although most of the questions here will have been submitted anonymously, this page also serves to answer some questions of general interest to those on the course.
When asking questions, please do not simply paste in your code and ask what is wrong with it, as these types of question are hard to answer in a public forum (resulting in either no answer, or an answer which will only be of use to the person asking the question). In many cases, a question about code can be formulated in general terms. For example, if you have an `IF' block that results in an `incorrect indentation' error, you might ask, ``what is the correct syntax for an occam `if' statement ?''. Questions of this form are easier to answer, and are meaningful to all.
Questions that are not suitable for these public pages (i.e. those that contain assignment-specific code), should be mailed to your seminar-leader.
Submission reference: IN839
My suspend process works but is giving some very strange output. The code is as follows:
... code deleted
It stops the program just fine but will not allow me to resume. To try and
test it a little bit i changed the last line from "SKIP
" to
"out ! x
", just to see what was pending on the output channel.
This allows the program to resume on the next keypress.
However: in every case (despite how long I wait before resuming),
the next number output is 43 and the other output from the
program is not what I'm expeciting. For example:
1 1 4 2 3 49 <-- pause here 43 46 135 43 89 181 <-- resume key 3 92 188
Do you have any idea why this may be happening? Or more to the point, is my suspend process along the right lines?
Sorry - your code fragment is deleted for the same reason as in the previous question.
However, your suspend process polls its control channel and, once it suspends operations, that's all it does. It spins forever, never releasing the processor and, so, your keyboard monitor process never gets back in to allow you to stop it! [Actually, that looks like a bug in the Transterpreter, which should re-schedule after a failed poll attempt. We will look into that.]
But that doesn't let you off the hook. That poll here is not needed ... and, if you have to poll, you shouldn't just spin ... without engaging (communicating) with some other process ... or having a (small) timeout ... in the polling loop. Then, everything gets scheduled again!
Putting a communication in your polling loop is what you do in the "test it a little bit" you describe. However, you output an unitialised variable and that accounts for the mysterious "43" you see. Uninitialised variables have no default value - the compiler warns you if it sees you using them. [I suspect our Transterpreter people decided it would be amusing to set them to 42, :), which they are absoultely entitled to do ...]
Keywords: q4
Submission reference: IN842
Is is ok if my 's' keypress doesn't use blackholes? It doesn't seem to cause any problems not using it, but I just wanted to make sure it was ok as you mentioned it during the class
Yes. If I mentioned it in class, it wasn't to do with this assignment. Sorry - I can't remember doing that. There is no need for "black holes" in this assessment.
Keywords: q4
Submission reference: IN852
Is there anyway to use the numbers
, integrate
and
pairs
processes in a SEQ
?
For SEQ
, I can't figure out how to make them work, but they are fine for a PAR
.
But I can't figure out how to stop them in a PAR
, I would use an IF
statement if it was a SEQ
; but I'm really stuck and I spent all day trying
to make it work ...
No, there is now way to use the numbers
, integrate
and
pairs
processes in a SEQ
. The code:
SEQ numbers (a!) integrate (a?, b!) pairs (b?, c!) ... etc.
first runs the numbers
process and waits for that to finish ... which
it never does ... so the integrate
never runs ... which means
there is no process taking the output from numbers
... which means
that numbers
never even manages to output its first number!
So, trying to run these processes in sequence ... that is a pretty fundamental error.
By "stopping them", I assume you mean blocking them so that the output stream is frozen. To do this, no change at all to those processes is required! You just have to put something in their output stream - an obstacle ("pause") process that you can switch between allowing traffic through or blocking it. To block traffic, simply don't input anything and, therefore, don't forward anything.
If by "stopping" you mean the last modification (response to a 'q' keypress), just terminate the obstacle process. Now, there's an "air-gap" in the wiring and no data can flow and pressing further keys cannot repair that! To see deadlock immediately, you should also terminate the keyboard monitor process - otherwise, that's still alive waiting for keyboard input.
Keywords: q4
Submission reference: IN854
Why does my code just print:
0 1 2 3
then deadlocks?
If I change the printstream
call to connect to the f
channel (rather than c
), then it just prints:
0
then deadlocks?
If I change it to connect g
, then it just deadlocks before printing!
Here's my code:
PROC starty (CHAN BYTE keyboard?, screen!, error!) CHAN INT a, b, c, d, e, f, g: CHAN INT x: INT xx: [3]CHAN INT q: WHILE TRUE PAR numbers (a!) delta (a?, b!, c!) integrate (b?, d!) delta (d?, e!, f!) pairs (e?, g!) print.stream (100000, c?, screen!) :
You never use your channel array q
, nor your channel x
nor your variable xx
.
The WHILE TRUE
betrays a bad misunderstanding! Its loop body is
a parallel network of processes - none of which ever finish. So, that network
never finishes ... so the loop body never finishes ... and your loop never loops!!
Just get rid of it.
In your code, numbers
outputs its first zero ... delta
forwards it to integrate
and print.stream
...
print.stream
prints it (first output line) ... integrate
adds it to zero and sends zero to the second delta
... which
forwards it to pairs
and channel f
.
Channel f
has no receiving process, so that's the end of the
second delta
which can't continue until it completes the output
on f
.
Meanwhile, pairs
takes in the zero and waits for its second input.
This is on its way ... as numbers
can now output its second number
(1) ... the first delta
forwards that to integrate
and print.stream
(which prints it - the second output line).
Meanwhile, integrate
adds it to its running sum and outputs that to
the second delta
... which, sadly, is blocked still trying to output
... which means that integrate
is now blocked.
Meanwhile, numbers
can now output its third number (3) ... etc.
Eventually, the first delta
gets jammed trying to output
to integrate
... so numbers
gets jammed trying to output to the first
delta
. Because, integrate
and pairs
have
internal concurrency, they can actually take in at least one more input than
outlined above (because they have parallel input and output processes).
This is why you get a couple more lines of output before complete deadlock
is establised, :(.
If you connect print.stream
to the g
channel,
the earlier delta
processes jam straight away as there is
no process taking their second output channels. Everything jams very
quickly ... though the first zero should get through to be printed?
Probably, the Transterpreter bails out with its deadlock message before
Windows gets around to showing the zero in the terminal window.
Your mistake is using print.stream
, which has only a single input
channel. You should be using print.streams
, whose code is given
in your starter file and which takes an array of input channels (one
from each delta
and one from the final pairs
).
Keywords: q4
Submission reference: IN851
Hi - I have just upgraded to vista and I have installed the Transterpreter and all works fine and all behavior is normal. However when I run the program the output from the program does not appear in the terminal window.
I have asked a friend to check the file on another machine and the code works fine. I have just tried installing the latest version Transterpreter on a XP machines and the same problem occurs. Any ideas.
This is from the Transterpreter team:
"One thing to try is to make sure to resize theoccPlug
window. There is currently a bug where the terminal window will not appear at all unless theoccPlug
window itself is big enough to contain it. I am working on fixing this, along with a number of other issues.
"Other than that we have not ever tried the Transterpreter in Vista nor are we likely to have access to any Vista machines soon(?). Perhaps the student can get in touch with us directly if he's interested in helping with testing and ironing out futher Vista bugs..."
Keywords: transterpreter
Referrers: Question 26 (2006)
Submission reference: IN858
Ref. to Question 25 (2006), I've been running it in Vista for a while and it all works 100% so long as you're running the latest Java release.
:)
Keywords: transterpreter
Submission reference: IN861
Hi I need some help! I spent ages trying to get this to work, but it doesnt seem to want to. I'm still stuck on the reset numbers to zero, so I have some questions!! [Stuff omitted.]
[Editor: the above question was posted at 14:00:02 on the day of the submission deadline ... the following was at 14:25:44.]
Hi, I know this is cutting it a bit fine but I've been stuck for days with question 3. When I run the program, the 'i' and 'n' keys work as expected however, when I press 'p', the program output doesnt change and the keyboard is unresponsive. [Stuff omitted.]
Sorry - that was cutting it too fine! We were attending a Board of Studies meeting from 2:00-4:30pm that afternoon ... and 4:30 was after the deadline. We try to turn around answers as quickly as possible - but cannot promise to do so within the day.
The story has a happy ending for at least one of the questioners ... who followed up an hour later with:
"Forget the last question, i figured it out!! Finally!"
:)
Keywords: q4
Submission reference: IN871
Is there any chance we can get our raptor quota increased? I couldnt get 128MB free on my raptor account for the VM we needed to run the robotics occam, the lowest I could get it down to without deleting files from other courses was 90MB and our limit is 200MB.
Done! The raptor filespace limit for all students taking Co631 has been increased to 350MB.
Keywords: robodeb , transterpreter
Submission reference: IN872
Sorry if theres a real obvious anwser to this question that I'm missing but is there a way for me to save(/load) the occ files a create in RoboDeb to my Windows drive? (for the sake of organisation and keeping backups)
If the directory structure for RoboDeb stored somewhere on my Windows install?
If this isn't possible I can always FTP or email my files so not too much of a problem but is it safe to assume that anything save when using the VMWare will be secure and wont dissappear for next time I use RoboDeb? Thanks.
The VMWare VM is "safe" to store your files on in one sense, and "unsafe" in others.
The VM saves its data where you installed it to. For most of you (all of you?) this was in your Raptor filespace. So, there is a file that represents "your" part of the VM virtual disk. It contains your data. So, as long as you don't delete *that*, you can launch the VM and work with your files. If that file is on Raptor, at least it is backed up.
Now, you're right in thinking that this is not exactly the best place to put your work. There are a few ways you can get things on/off the VM; you'll have to decide what works best for you.
It is *possible* you will run into the University proxy if you do this. In your home directory, there should be a .subversion directory. In there, you'll have to uncomment the lines dealing with proxies and set them correctly for the wwwcache. I cannot, right now, recall if this is/was an issue or not. If your repository is on Raptor, it might not/should not be an issue at all.
My point is, anything you can do from a Linux host, you can do from RoboDeb. For examle, you might run a Samba server on RoboDeb, and mount the VM from the Windows host. If you come up with a backup approach you'd like to take, by all means, do it. Drop a note if you come up with a particularly good solution. We're working on revising RoboDeb now, and would welcome suggestions from you as to how to get around this best.
Hopefully, that answers the question. If you have more questions, you can either post them here, or you can join the tvm-discuss mailing list:
https://ssl.untyped.com/cgi/mailman/listinfo/tvm-discuss
This is a good place to ask questions and get answers that pertain to the Transterpreter itself or the RoboDeb VM. The whole TVM team is on that list, and we're glad to help make the tools better in any way that we can.
Keywords: robodeb , transterpreter
Referrers: Question 35 (2006)
Submission reference: IN904
Over the past few weeks I have gained a useful insight into concurrency and generally enjoyed the content of the module. However as the course progresses its seems that the content of the module has started to slope more towards learning the Occam semantics rather than generic concurrency issues.
You explained clearly why we are using Occam to teach these issues but I feel for the course to become useful to students you need to relate/compare these issues against a more widely adopted language/style.
You frequently mention Java's problem with its concurrency off topic; but I feel that if you spent some time demonstrating these problems this would benefit us greatly. At the precise moment I don't feel that I can apply much of what has been taught to more common language platforms as not much as been related to traditional approaches like Java threads, this view is held by the majority of students I have spoken to. For example I would be interested to know if languages such as .NET, Python, C etc are more suitable than others for building systems with a high level of concurrency.
Does the module have any intentions of relating the lessons learned from Occam back to more widely adopted approaches?
Please don't take these comments negatively as I believe overall the course is delivered very well. :-)
Thank you very much for your question - you have put a lot of care and thought into it and I'll try to do the same in this answer!
There are several quite different concurrency models currently being used.
By far the most common is threads-and-locks: in this, multiple threads
share a single common memory space - some of which they use privately and some
for communicating with each other. This is supported by standard libraries for C
and C++ (e.g. Posix - offering spinlocks, mutexes, semaphores)
and Java monitors (synchronized
, wait
,
notify
and notifyAll
). The primitives make no statement
as to when and how to use them ... but they are easy to learn. However,
they are notoriously difficult to reason about and, hence, to use correctly.
Too much locking leads to deadlock; too little locking leads to system corruption
and, eventual, failure. The circumstances leading to either of these conditions
are usually unrepeatable ... which makes debugging near impossible.
Almost all systems programmed at this level are wrong.
Because all systems above trivial levels of complexity have to deal with
concurrency and are programmed at this level, almost all systems are wrong.
This causes loss of lots of money and loss of life. For comments, see:
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
and search for the words: "extreme caution"!
Threads-and-locks was the first (and is still the main) approach to concurrency. Other approaches had to be found to improve the state of the art. We are studying one based upon the idea of communicating processes, which only operate on private memory and use a range of special mechanisms for communication and synchronisation (e.g. channels and barriers). Examples of this include Microsoft's Singularity:
http://research.microsoft.com/os/singularity/
which founds its security/simplicity on occam-pi ideas ("Software Isolated Processes", though at a much coarser granularity and with their own language derived from C#). There is also the Actor concept:
http://en.wikipedia.org/wiki/Actor_model
which is trivial to apply from occam-pi. And there is occam-pi itself (and JCSP), based on the formal process algebras of CSP and the pi-calculus.
But there are still other models - such as Software Transactional Memory:
http://en.wikipedia.org/wiki/Software_transactional_memory
derived from ideas in databases and recently favoured by the functional programming community (e.g. with Haskell). Another contender comes from the Join Calculus and is exemplified by Microsoft's C-omega language (prevously called "Polyphonic C#"):
http://research.microsoft.com/comega/ http://research.microsoft.com/~nick/polyphony/intro.htm
So, there are lots of models out there and there will be more to come. We're teaching something we believe is the simplest and most powerful available right now. We don't want to teach you stuff we know doesn't work, even though that's what most of the world uses. Having taught you the good stuff and given you the chance to mature and appreciate working with it, you will get a shock when you try the bad stuff, :(.
If you browse around the materials in the course folder on raptor:
\courses\co631\etc\README.txt
you will find a tutorial on the standard Java concurrency (threads-and-locks) model ("Java Threads in the Light of occam/CSP"):
\courses\co631\etc\java-occam-csp.pdf
This presents the basic mechanisms, but points out real difficulties in their use. The second half of this paper (Section 3) shows how to drop the occam/CSP model on top of all that bad stuff. It assumes you have some knowledge of the occam-pi model of processes and channels - which you do! The implementation of channels is rather tricky (all that bad stuff has to be used) - but once done, you don't need to bother with it again and work at the occam model level, :). Eventually, that work led to the JCSP package libraries for Java (which I'll try to leave time in the module to present). Similar libraries for C (CCSP) and C++ (C++CSP) have also been made.
I hope this has helped put things in better context. Mastering the good ideas gives us a much better chance of surviving the bad ones, simply because we know that we have to be so very careful (and because we have a good model that shows us - technically - how to take that care).
Submission reference: IN945
This is probably an issue with my icky code but it is worth checking. KRoC produces:
Fatal-occ21-q7.occ(162)- internal inconsistency found in routine "chan_direction_of -- not channel" ********************************************************************* * The compiler has detected an internal inconsistency. * * Please email kroc-bugs@ukc.ac.uk with a copy of the code which * * broke the compiler, as this is probably a compiler bug. * * Please include the KRoC version and any other relevant details. * *********************************************************************
The offending code fragment is:
INITIAL MOBILE []CHAN BOOL down IS MOBILE [num.phils]CHAN BOOL:
So, what's wrong? Am I doing something silly or did I find a bug in KRoC? (I highly suspect the former rather than the latter!). I've not yet tried in the Transterpreter, I guess this would be a good plan...
I'd expect the Transterpreter to fail in the same way &mdash it uses the same compiler. The problem is a compiler bug, of sorts. Dynamic mobile arrays of channels are not entirely well supported, as you've discovered. The issue is that the compiler isn't allowing you to refer to the whole array 'down' (when you attempt to pass it as a parameter to another procedure).
Unfortunately I'm way too busy at the moment to fix compiler bugs (until June/July at least), so you'll have to find another way to code what you want.. Sorry :-(. But logically, what you're trying to do is sane.
PHW writes: all you're trying to do (I think?) is declare an array of channels with 'num.phils' elements. In the above, you've adapted a line from 'print.streams' that declares an array whose size is computed at run-time (from the size of the channel array argument it was passed). Your adaptation is logical but, as Fred says, the compiler does not yet support all aspects of dynamic array allocation – such as for channels.
However, if 'num.phils' is a compiler-known value (not a variable set at run-time) – e.g. it is declared somewhere towards the top of your file by:
VAL INT n.phils IS 5:
then you can use it to declare a fixed-size array of anything, including channels, trivially – for example:
[num.phils]CHAN BOOL down:
That should be all you need? :)
Keywords: coursework , q7
Submission reference: IN984
Why do you have comments like this in code?:
--{{{ think ... code --}}}
isn't it "--" for comments?
The dash-pair is for the comment. The triplets of curly braces are for folding. You'll need an editor that understands folding to make any use of these, but vim/gvim (common) and origami (old) do, and I suspect there are others. It basically allows the editor to collapse whole chunks of code into single highlighted lines, making working with any sizeable bit of code a whole lot easier. People who haven't discovered folding are at a disadvantage in my opinion :-). Some editors also understand how to do folding based on language constructs, rather than explicit markup as is commonly used.
Keywords: folding
Submission reference: IN995
How do you create an array of strings? This is the closest I have got:
VAL [][]BYTE name.phil IS ["Bob", "James", "Fred", "Jane", "Amy"]:
But it doesn't work, and theres not a whole lot of information online :( Any hints?
See Question 104 (2003) in particular, and other questions relating to strings and string-handling. Lots of stuff on-line!
Keywords: strings , string-handling
Referrers: Question 58 (2007)
Submission reference: IN987
In the older answers, you mention model answers. Are these still available to look at?
OK - I've put model answers to completing q1.occ
,
q2.occ
, q3.occ
and q4.occ
in the raptor folder:
\courses\co631\answers\
Submission reference: IN997
I can't transfer text from the VMware to windows, even after reading the comments for the other question (Question 29 (2006))about this.
Where can I find "subversion"? I can't find it anywhere, and I searched all through my home directory on raptor, and there don't seem to be any folders holding any files that I save! It's driving me crazy!!!!
I just wanted to copy and paste some code to ask you a question, but even that's not working. My code (painstakingly copied by hand) is something like this:
-- proc that takes 1 chan int and returns 2 chan ints PROC chanDouble(CHAN INT in1?, in2?, out!) INT x1, x2, x3: CHAN INT cx: WHILE TRUE SEQ in1 ? x1 in2 ? x2 x3 := x1 + x2 cx ! x3 out ! cx :
It gives the error:
I/O list item 1 does not match protocol
and the line of the error is the "out ! cx
".
It's such a simple piece of code.
Why isnt it working?
And how can i use or find "subversion"?
Possibly the simplest way to move files between your VMWare linux environment
and anywhere else is to use the scp
command.
A Google search ("SCP manual") just found the following pages that should help
you do this:
http://amath.colorado.edu/computing/software/man/scp.html http://vanha.cc.utu.fi/english/diskspace/scpmanual.html
Subversion, which gives you a source control system, can be downloaded from:
http://subversion.tigris.org/
but I think I'd find scp
quicker to understand and use.
The comment above your PROC
is a little misleading.
Your PROC
takes three INT
carrying channels
and doesn't return anything – once invoked, it never returns
... because its WHILE TRUE
loop never terminates!
Nevertheless, it can do useful work by accepting stuff on its input
channels and generating stuff on its output channel.
Your internal declaration of CHAN INT cx:
looks very wrong.
If we declare an internal channel, it's because the implementation is going
to go PAR
allel, with one sub-process being given the writing
(output) end of the internal channel and another getting the reading (input)
end. That doesn't happen in your code: in its loop cycle, it inputs two
numbers from its two input channels, adds them together and then tries to
output them down the internal channel (cx ! x3
).
At that point, it would get stuck since there is no process running in
parallel and inputting from that internal channel.
However, your code doesn't compile because the line, out ! cx
,
says: "output the channel cx
down channel out
".
But we can only send integers down channel out
–
not channels! Hence, the compiler's complaint.
Maybe you meant to output the result of the addition down (the external)
channel out
? If so, all you need is:
out ! x3
Keywords: robodeb
Submission reference: IN988
I am probably being dense, but is there a way to get code like this to work:
PAR left ! TRUE out ! got.left right ! TRUE out ! got.right
or:
PAR SEQ left ! TRUE out ! got.left SEQ right ! TRUE out ! got.right
Where out
is a channel to the PROC
that animates everything.
I currently get an error about parallel output to out
if I put SEQ
s in, but is there a way to do it?
Your first code fragment is syntactically malformed – indentation errors.
Your second has a legal syntactic structure but has a semantic error –
it tries to have two processes outputting in parallel to the same
channel (out
).
The simplest solution is not to bother reporting to the animation process when a philosopher manages to pick up an individual fork! The fork processes will be doing that – as they are picked up (or put down) – and the animation process will receive those reports. So, having the philosophers do that as well is redundant.
Only if you weren't equiping the forks with report channels would it be necessary for the philosophers to report fork movements. There are two ways to do that. Either give each philosopher two report channels: one through which it reports everything bar, say, left fork movements ... and one exclusive for reporting those latter. Or run a short-lived multiplexing process in parallel with the parallel hand movements (to pick up or put down forks), with two channels for reporting between the hand movements and the multiplexor. The multiplexor waits for just two reports, sending them to the external report channel and, then, terminates (along with the two hand movements).
I'd go for the simple solution!
Keywords: q7
Submission reference: IN996
Is there an easy way to pass Strings between processes? It seems that you
have to specify the length, such as [30]BYTE
, but this doesn't seem a
particularly amazing way to do it. Is there a way to say
[however-long-it-needs-to-be]BYTE
?
If this question is prompted for animating the dining philosophers,
relax – you don't have to pass strings around.
The college components (philosphers, forks and security guard) should
send coded messages to the animating process, which reacts by outputting
text strings to the screen.
The coded messages can be simple BYTE
s (or INT
s)
representing states like "I'm hungry", "I'm on the table"
or "I've counted in n philosophers".
However, if you have some other reason for passing around strings, use a counted array protocol as presented in the last lecture. For example:
PROTOCOL STRING IS BYTE::[]BYTE :
This protocol (actually message structure) allows you to pass
around arrays of BYTE
s (e.g. strings), preceded with
a (BYTE
) count of how long they are.
Since the largest byte value is 255, that is the longest string that
can be passed with this protocol.
This means that, so long as a receiving array for such a message has
255 elements, it will always be large enough – i.e. there will
be no run-time array bounds overflow.
Submission reference: IN1002
I'm presuming BYTE
s are a bit more efficient than INT
s
for passing small numbers around the PROC
s and am therefore using them.
Assuming they are better, can I change the INT
s in security to
BYTE
s, so I don't get all confused?
A BYTE
communication shifts one byte –
an INT
communication shifts four bytes.
So, yes, it is more efficient to shift only one when
there is no need to shift four.
But, in this application, you would need a pretty sharp
sense of time to notice!
The proper reason is an engineering one: if you can do the job with less work, do so! This is a variety of Ockham's Razor: don't invent stuff that you don't need – doing so opens unnecessary scope for error. It was precisely by breaking this rule that the Arianne 5 rocket crashed in 1996, fortunately on its first test flight (though not so fortunately for the four fly-formation telescopes hitching a ride).
The security guard only has 5 numbers to report – either 0, 1,
2, 3 or 4 philosophers have been counted through to the dining room.
These are all in the range of BYTE
values.
So, yes, let the security guard count and report in
BYTE
s – not INT
s.
Keywords: q7
Submission reference: IN1003
Using something like this:
PAR i = 0 FOR 5
Can I make it produce Strings like "Fork 1"
,
"Fork 2"
etc., with some sort of concatenation?
Such as "Fork " + 1
in Java?
No.
What on earth might a PAR
replicator have to do with that?
Strings are not a primitive type in occam-pi. There are byte arrays that we use for them, but there are no built-in (or library) operators – such as concatenation. The follow-on module, Stage 3 Co632, introduces mobile and dynamically sized and allocated arrays. These, rather than the fixed-sized arrays used in this module, are from what strings (and a string library) should be made.
Meanwhile, please be assured that there is no need for any string
concatenation when animating the dining philosophers.
Only your animation process should deal with strings – outputting
them to a screen channel.
To output the string "Fork 2"
, where 2 is the value of
a variable (say i
):
SEQ out.string ("Fork ", 0, screen!) out.int (i, 0, screen!)
Keywords: strings
Submission reference: IN1004
Not a question really, but &ndash keeps appearing in your answers :S I presume you mean - ?
My mistake! I'd missed the semi-colon off the HTML symbol:
& n d a s h ;
spelt out above with spaces – so you can see it! Your browser should render this as a medium-sized dash (a bit longer than a plain text "-").
My browser (Firefox 1.5.0.7) was forgiving of the missing semi-colons and rendered it anyway – so I didn't see my mistake. It should all be corrected now. Many thanks.
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. |