Skip to content

Commit

Permalink
Update tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
smoynes committed Nov 23, 2023
1 parent 07d1792 commit 5cfa7ad
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 42 deletions.
73 changes: 44 additions & 29 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ In this tutorial you will learn how to:
language; and
- execute the resulting program.

## Installation ##
## Dependencies ##

To install the 𝔼𝕃𝕊𝕀𝔼, you will need Go version 1.21, or greater. To check if you
have a good version, run:
To install 𝔼𝕃𝕊𝕀𝔼, you will need Go version 1.21, or greater. You can check if
you have a recent enough by running:

```console
$ go version
go version go1.21.4 darwin/amd64
```

If you do not have Go 1.21 installed, you can get it from the Go download page:
https://go.dev/dl/
<https://go.dev/dl/>. Alternatively, you might be able to use a package manager
for your platform to install a compatible Go version.

Alternatively, you might be able to use a package manager for your platform to
install a compatible Go version.
## Installation ##

With Go installed, you can now download, build, and install 𝔼𝕃𝕊𝕀𝔼. Run:

Expand All @@ -32,8 +32,8 @@ $ go install github.com/smoynes/elsie@latset
```

Go will store the program in its `bin` directory. By default, the location is
configured with the `GOBIN` environment variable or the `GOPATH/bin` directory.
You can check with:
configured with the `GOBIN` environment variable or, if not set, the default is
the `GOPATH/bin` directory. You can check with:

```console
$ go env GOPATH GOBIN
Expand All @@ -42,8 +42,8 @@ $ go env GOPATH GOBIN
```

In this case `GOBIN` is unset so the `elsie` command is installed in
`/home/elise/bin/1.21.4/bin`. This directory may or may not be present in your
shell's `PATH`. For the sake of the tutorial, we'll assume it, is but do consult
`/home/elise/go/1.21.4/bin`. This directory may or may not be present in your
shell's `PATH`. For the sake of the tutorial, we'll assume it is, but do consult
your configuration to add this directory to your system or user configuration.

Say hello:
Expand All @@ -58,10 +58,10 @@ Usage:
elsie <command> [option]... [arg]...

Commands:
exec run a program
asm assemble source code into object code
demo run demo program
help display help for commands
exec run a program
asm assemble source code into object code
demo run demo program
help display help for commands

Use `elsie help <command>` to get help for a command.
exit status 1
Expand All @@ -71,7 +71,7 @@ exit status 1
## Running the demo ##

𝔼𝕃𝕊𝕀𝔼 includes a silly, hard-coded demo that you can run it with the `demo`
command. You should see a few shocked characters printed and a message of
command. You should see a few shocked characters slowly printed and a message of
gratitude. This is what success looks like:

```console
Expand All @@ -84,11 +84,17 @@ MACHINE HALTED!

```

The demo initialize the machine, outputs a message using BIOS system-calls, and
halts the machine. It is not much, but it is an honest program.
The demo does quite a bit of work for little reward. In detail, the demo:

- initializes the virtual machine;
- loads a system image and the program into memory;
- executes the program instructions in sequence according to the control flow;
- outputs a message using BIOS system-calls, themselves small programs;
- halts the virtual machine.

You can also run the demo with additional logging enabled. You will see logs for
machine startup and its state after executing each instruction.
It is not much, but it is an honest program. You can also run the demo with
additional logging enabled. You will see logs for machine startup and its state
after executing each instruction.

```console
$ elsie demo -log
Expand All @@ -112,15 +118,17 @@ $ elsie demo -log

## Writing a program ##

While a hard-coded demo is impressive, it is also deeply unsatisfying. It is not
enough to interpret a pointless, pre-written program -- we also want to write
our own pointless programs for our machine to interpret.
A hard-coded demo is both impressive andd deeply unsatisfying. It is not enough
to interpret a pointless, pre-written program -- we also want to write our own
pointless programs!

𝔼𝕃𝕊𝕀𝔼 includes a translator that lets us write programs in a simple assembly
dialect called <tt>LC3ASM</tt>. We will use the `elsie asm` command to run the
assembler and produce machine code. Later, we will execute the program.
dialect called <abbr>LC3ASM</abbr>. We will use the `elsie asm` command to run
the assembler and produce machine code. Later, we will use its output to execute
our program. With this simple command, the full power of the LC-3 is at the tips
of our fingers.

First save a file named `example.asm`
First, save a file named `example.asm`

```asm
.ORIG x3000
Expand All @@ -137,24 +145,31 @@ ASCII .DW 48
.END
```

We'll look at the code in detail later -- for now, we'll run the assembler on
We'll look at the code in detail later on -- for now, we'll run the assembler on
it:

```console
$ elsie asm example.asm
```

No output is produced if successful. You may see error messages if you copied
the code incorrectly. The output is stored in a file called `a.o`, for lack of a
better default. It's contents should look like:
the code incorrectly or something else went wrong. You can run `elsie asm -log
example.asm` if you would like a chattier assembler.

The output is stored in a file called `a.o`, for lack of a better default. It's
contents should look like:

```
:143000002207240704041081f021127f0ffbf02500050030d9
:00000001ff
```

As you might be able to guess, the machine code is encoded as bytes in something
hexadecimal-y. This is all that is needed to be executed.
hexadecimal-y. This is object code, or byte code as it is sometimes called. This
is all that is needed to execute: 𝔼𝕃𝕊𝕀𝔼 loads this data into memory and begins
executing instructions herein.

To execute the object code:

```console
$ elsie exec a.o
Expand Down
12 changes: 12 additions & 0 deletions examples/example.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.ORIG x3000
LD R1,COUNT
LD R2,ASCII
LOOP BRz EXIT
ADD R0,R2,R1
TRAP x21
ADD R1,R1,#-1
BR LOOP
EXIT HALT
COUNT .DW 5
ASCII .DW 48
.END
2 changes: 1 addition & 1 deletion internal/asm/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
//
// The generator starts at the beginning of the parsed-syntax table, generates code for each
// operation, and then writes the generated code to the output (usually, a file). Use Encode to
// write as hex-encoded ASCII files or use WriteTo write binary object-code to a buffer.
// write as hex-encoded ASCII files or use WriteTo write to binary object-code to a buffer.
//
// During the generation pass, any syntax or semantic errors that prevent generating machine code
// are immediately returned. The errors are wrapped in SyntaxErrors and may be tested and retrieved
Expand Down
26 changes: 16 additions & 10 deletions internal/cli/cmd/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func Assembler() cli.Command {
}

type assembler struct {
log bool
debug bool
output string
}
Expand All @@ -41,6 +42,7 @@ Assemble source into object code.`)

func (a *assembler) FlagSet() *cli.FlagSet {
fs := flag.NewFlagSet("asm", flag.ExitOnError)
fs.BoolVar(&a.log, "log", false, "enable logging")
fs.BoolVar(&a.debug, "debug", false, "enable debug logging")
fs.StringVar(&a.output, "o", "a.o", "output `filename`")

Expand All @@ -49,7 +51,9 @@ func (a *assembler) FlagSet() *cli.FlagSet {

// Run calls the assembler to assemble the assembly.
func (a *assembler) Run(ctx context.Context, args []string, stdout io.Writer, logger *log.Logger) int {
if a.debug {
if a.log {
log.LogLevel.Set(log.Info)
} else if a.debug {
log.LogLevel.Set(log.Debug)
}

Expand All @@ -65,14 +69,15 @@ func (a *assembler) Run(ctx context.Context, args []string, stdout io.Writer, lo
return 1
}

logger.Info("Parsing source", "file", fn)
parser.Parse(f)
}

logger.Debug("Parsed source",
"symbols", parser.Symbols().Count(),
"size", parser.Syntax().Size(),
"err", parser.Err(),
)
logger.Debug("Parsed source",
"symbols", parser.Symbols().Count(),
"size", parser.Syntax().Size(),
"err", parser.Err(),
)
}

if parser.Err() != nil {
logger.Error("Parse error", "err", parser.Err())
Expand All @@ -89,11 +94,10 @@ func (a *assembler) Run(ctx context.Context, args []string, stdout io.Writer, lo
symbols := parser.Symbols()
syntax := parser.Syntax()
generator := asm.NewGenerator(symbols, syntax)
buf := bufio.NewWriter(out)

logger.Debug("Writing object", "file", a.output)

buf := bufio.NewWriter(out)

objCode, err := generator.Encode()
if err != nil {
logger.Error("Compile error", "out", a.output, "err", err)
Expand All @@ -106,12 +110,14 @@ func (a *assembler) Run(ctx context.Context, args []string, stdout io.Writer, lo
return -1
}

logger.Debug("Wrote object", "file", a.output, "size", wrote)

if err := buf.Flush(); err != nil {
logger.Error("I/O error", "out", a.output, "err", err)
return -1
}

logger.Debug("Compiled object",
logger.Info("Compiled object",
"out", a.output,
"size", wrote,
"symbols", symbols.Count(),
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/cmd/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ Commands:`)

for _, cmd := range h.cmd {
fs := cmd.FlagSet()
fmt.Fprintf(out, " %-20s %s\n", fs.Name(), cmd.Description())
fmt.Fprintf(out, " %-8s %s\n", fs.Name(), cmd.Description())
}

fmt.Fprintf(out, " %-20s %s\n", h.FlagSet().Name(), h.Description())
fmt.Fprintf(out, " %-8s %s\n", h.FlagSet().Name(), h.Description())
fmt.Fprintln(out)
fmt.Fprintln(out, "Use `elsie help <command>` to get help for a command.")

Expand Down

0 comments on commit 5cfa7ad

Please sign in to comment.