From f89a4592c973a798ec47eb07b3701844d9ede54e Mon Sep 17 00:00:00 2001
From: Freya Murphy <freya@freyacat.org>
Date: Tue, 25 Mar 2025 17:41:18 -0400
Subject: [PATCH] convert build system to zig

---
 .gitignore   |   2 +
 Makefile     | 328 +++--------------------------------------------
 boot/boot.ld |   9 ++
 build.zig    | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++
 kernel/sio.c |  12 +-
 5 files changed, 388 insertions(+), 314 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 boot/boot.ld
 create mode 100644 build.zig

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db16bde
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.zig-cache
+bin
diff --git a/Makefile b/Makefile
index 67353ce..27f2133 100644
--- a/Makefile
+++ b/Makefile
@@ -1,319 +1,31 @@
-#
-# Makefile for the 20245 operating system.
-#
 
-########################################
-# Compilation/assembly definable options
-########################################
+.PHONY: build clean qemu
+.SILENT:
 
-#
-# General options:
-#   CLEAR_BSS           include code to clear all BSS space
-#   GET_MMAP            get BIOS memory map via int 0x15 0xE820
-#   OS_CONFIG           OS-related (vs. just standalone) variations
-#   FORCE_INLINING      force "inline" functions to be inlined even if
-#                       we aren't compiling with at least -O2
-#   MAKE_IDENTITY_MAP   Compile vmtables.c with an "identity" page
-#                       map table for the first 4MB of the address space.
-#
+QEMU = qemu-system-i386
+QEMUOPTS = -drive file=bin/disk.img,index=0,media=disk,format=raw
 
-GEN_OPTIONS = -DCLEAR_BSS -DGET_MMAP -DOS_CONFIG
-
-#
-# Debugging options:
-#   ANNOUNCE_ENTRY      announce entry and exit from kernel functions
-#   RPT_INT_UNEXP       report any 'unexpected' interrupts
-#   RPT_INT_MYSTERY     report interrupt 0x27 specifically
-#   TRACE_CX            context restore tracing
-#   SANITY=n            enable "sanity check" level 'n' (0/1/2/3/4)
-#   T_*                 tracing options (see below)
-#
-# Some modules have their own internal debugging options, described
-# in their introductory comments.
-#
-# Define SANITY as 0 for minimal runtime checking (critical errors only).
-# If not defined, SANITY defaults to 9999.
-#
-
-DBG_OPTIONS = -DRPT_INT_UNEXP
-# DBG_OPTIONS += -DTRACE_CX
-
-#
-# T_ options are used to define bits in a "tracing" bitmask, to allow
-# checking of individual conditions. The following are defined:
-#
-#   T_PCB                   PCB alloc/dealloc
-#   T_VM                    VM-related tasks
-#   T_QUE                   PCB queue manipulation
-#   T_SCH, T_DSP            Scheduler and dispatcher
-#   T_SCALL, T_SRET         System call entry and exit
-#   T_EXIT                  Process exit actions
-#   T_FORK, T_EXEC          Fork and exec actions
-#   T_INIT                  Module init function tracing
-#   T_KM, T_KMFR, T_KMIN    Kmem module tracing
-#   T_SIO, T_SIOR, T_SIOW   General SIO module checks
-#   T_USER, T_ELF           User module operations
-#
-# You can add compilation options "on the fly" by using EXTRAS=thing
-# on the command line.  For example, to compile with -H (to show the
-# hierarchy of #includes):
-#
-#	make EXTRAS=-H
-#
-
-TRACE_OPTIONS = -DT_INIT -DT_USER -DT_ELF -DT_KMIN -DT_VM
-
-USER_OPTIONS = $(GEN_OPTIONS) $(DBG_OPTIONS) $(TRACE_OPTIONS) $(EXTRAS)
-
-##############################################################
-# YOU SHOULD NOT NEED TO CHANGE ANYTHING BELOW THIS POINT!!! #
-##############################################################
-
-#
-# Compilation/assembly control
-#
-
-#
-# We only want to include from the common header directory
-#
-INCLUDES = -I./include
-
-#
-# All our object code will live here
-# 
-BUILDDIR = build
-LIBDIR = $(BUILDDIR)/lib
-
-#
-# Things we need to convert to object form
-#
-SUBDIRS := 
-
-#
-# Compilation/assembly/linking commands and options
-#
-CPP = cpp
-CPPFLAGS = $(USER_OPTIONS) -nostdinc $(INCLUDES)
-
-#
-# Compiler/assembler/etc. settings for 32-bit binaries
-#
-CC = gcc -pipe
-CFLAGS = -m32 -fno-pie -std=c99 -fno-stack-protector -fno-builtin -Wall -Wstrict-prototypes -MD $(CPPFLAGS)
-# CFLAGS += -O2
-
-AS = as
-ASFLAGS = --32
-
-LD = ld
-LDFLAGS = -melf_i386 -no-pie -nostdlib -L$(LIBDIR)
-
-AR = ar
-#ARFLAGS = rvU
-ARFLAGS = rsU
-
-# other programs we use
-OBJDUMP = objdump
-OBJCOPY = objcopy
-NM      = nm
-READELF = readelf
-PERL    = perl
-
-# delete target files if there is an error, or if make is interrupted
-.DELETE_ON_ERROR:
-
-# don't delete intermediate files
-.PRECIOUS:	%.o $(BUILDDIR)/boot/%.o $(BUILDDIR)/kernel/%.o \
-	$(BUILDDIR)/lib/%.o $(BUILDDIR)/user/%.o
-
-#
-# Update $(BUILDDIR)/.vars.X if variable X has changed since the last time
-# 'make' was run.
-#
-# Rules that use variable X should depend on $(BUILDDIR)/.vars.X.  If
-# the variable's value has changed, this will update the vars file and
-# force a rebuild of the rule that depends on it.
-# 
-
-$(BUILDDIR)/.vars.%: FORCE
-	echo "$($*)" | cmp -s $@ || echo "$($*)" > $@
-
-.PRECIOUS:	$(BUILDDIR)/.vars.%
-
-.PHONY:	FORCE
-
-#		
-# Transformation rules - these ensure that all necessary compilation
-# flags are specified
-#
-# Note use of 'cpp' to convert .S files to temporary .s files: this allows
-# use of #include/#define/#ifdef statements. However, the line numbers of
-# error messages reflect the .s file rather than the original .S file. 
-# (If the .s file already exists before a .S file is assembled, then
-# the temporary .s file is not deleted.  This is useful for figuring
-# out the line numbers of error messages, but take care not to accidentally
-# start fixing things by editing the .s file.)
-#
-# The .c.X rule produces a .X file which contains the original C source
-# code from the file being compiled mixed in with the generated
-# assembly language code.  Can be helpful when you need to figure out
-# exactly what C statement generated which assembly statements!
-#
-
-.SUFFIXES:	.S .b .X .i
-
-.c.X:
-	$(CC) $(CFLAGS) -g -c -Wa,-adhln $*.c > $*.X
-
-.c.s:
-	$(CC) $(CFLAGS) -S $*.c
-
-#.S.s:
-#	$(CPP) $(CPPFLAGS) -o $*.s $*.S
-
-#.S.o:
-#	$(CPP) $(CPPFLAGS) -o $*.s $*.S
-#	$(AS) $(ASFLAGS) -o $*.o $*.s -a=$*.lst
-#	$(RM) -f $*.s
-
-.s.b:
-	$(AS) $(ASFLAGS) -o $*.o $*.s -a=$*.lst
-	$(LD) $(LDFLAGS) -Ttext 0x0 -s --oformat binary -e begtext -o $*.b $*.o
-
-#.c.o:
-#	$(CC) $(CFLAGS) -c $*.c
-
-.c.i:
-	$(CC) -E $(CFLAGS) -c $*.c > $*.i
-
-#
-# Location of the QEMU binary
-#
-QEMU = /home/course/csci352/bin/qemu-system-i386
-
-# try to generate a unique GDB port
-GDBPORT = $(shell expr `id -u` % 5000 + 25000)
-
-# QEMU's gdb stub command line changed in 0.11
-QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
-	then echo "-gdb tcp::$(GDBPORT)"; \
-	else echo "-s -p $(GDBPORT)"; fi)
-
-# options for QEMU
-#
-# run 'make' with -DQEMUEXTRA=xxx to add option 'xxx' when QEMU is run
-#
-# does not include a '-serial' option, as that may or may not be needed
-QEMUOPTS = -drive file=disk.img,index=0,media=disk,format=raw $(QEMUEXTRA)
-
-########################################
-# RULES SECTION
-########################################
-
-#
-# All the individual parts
-#
-
-#
-# We have a bit of a chicken/egg problem here. When we create the
-# user.img file, a new version of include/userids.h is generated
-# in build/new_userids.h; this is compared to the existing userids.h
-# file, and if they differ, it is copied into include/userids.h.
-# This, unfortunately, should trigger a rebuild of anything that 
-# includes <userids.h>, but that is all of the user/*.c files along
-# with kernel/kernel.c. We could move the user.img creation earlier,
-# which would automatically be incorporated into the build of the
-# kernel, but it wouldn't automatically trigger recreating the
-# userland stuff. We settle for having the build process tell the
-# user that a rebuild is required.
-#
-
-all:	lib bootstrap kernel userland util user.img disk.img
-
-# Rules etc. for the various sections of the system
-include lib/Make.mk
-include boot/Make.mk
-include user/Make.mk
-include kernel/Make.mk
-include util/Make.mk
-
-#
-# Rules for building the disk image
-#
-
-disk.img: $(BUILDDIR)/kernel/kernel.b $(BUILDDIR)/boot/boot user.img BuildImage
-	./BuildImage -d usb -o disk.img -b $(BUILDDIR)/boot/boot \
-		$(BUILDDIR)/kernel/kernel.b 0x10000 \
-		user.img 0x30000
-
-#
-# Rules for running with QEMU
-#
-
-# how to create the .gdbinit config file if we need it
-.gdbinit: util/gdbinit.tmpl
-	sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@
-
-# "ordinary" gdb
-gdb:
-	gdb -q -n -x .gdbinit
-
-# gdb with the super-fancy Text User Interface
-gdb-tui:
-	gdb -q -n -x .gdbinit -tui
-
-qemu: disk.img
+qemu: bin/disk.img
 	$(QEMU) -serial mon:stdio $(QEMUOPTS)
 
-qemu-nox: disk.img
-	$(QEMU) -nographic $(QEMUOPTS)
-
-qemu-gdb: disk.img .gdbinit
-	@echo "*** Now run 'gdb'." 1>&2
-	$(QEMU) -serial mon:stdio $(QEMUOPTS) -S $(QEMUGDB)
-
-qemu-nox-gdb: disk.img .gdbinit
-	@echo "*** Now run 'gdb'." 1>&2
-	$(QEMU) -nographic $(QEMUOPTS) -S $(QEMUGDB)
-
-#
-# Create a printable namelist from the kernel file
-#
-# kernel.nl:   only global symbols
-# kernel.nll:  all symbols
-#
-
-kernel.nl: $(BUILDDIR)/kernel/kernel
-	nm -Bng $(BUILDDIR)/kernel/kernel.o | pr -w80 -3 > kernel.nl
-
-kernel.nll: $(BUILDDIR)/kernel/kernel
-	nm -Bn $(BUILDDIR)/kernel/kernel.o | pr -w80 -3 > kernel.nll
-
-#
-# Generate a disassembly
-#
-
-kernel.dis: $(BUILDDIR)/kernel/kernel
-	objdump -d $(BUILDDIR)/kernel/kernel > kernel.dis
-
-#
-# Cleanup etc.
-#
 
 clean:
-	rm -rf $(BUILDDIR) .gdbinit *.nl *.nll *.lst *.i *.X *.dis
+	rm -fr .zig-cache
+	rm -fr bin
 
-realclean:	clean
-	rm -f LOG *.img $(UTIL_BIN)
+build:
+	zig build
 
-#
-# Automatically generate dependencies for header files included
-# from C source files.
-#
-$(BUILDDIR)/.deps: $(foreach dir, $(SUBDIRS), $(wildcard $(BUILDDIR)/$(dir)/*.d))
-	@mkdir -p $(@D)
-	$(PERL) util/mergedep.pl $@ $^
+bin/boot.bin: build
+	cd bin && \
+		objcopy -S -O binary -j .text boot boot.bin
 
--include $(BUILDDIR)/.deps
+bin/user.img: build
+	cd bin && \
+		./mkblob init shell
+
+bin/disk.img: build bin/boot.bin bin/user.img
+	cd bin && \
+		./BuildImage -d usb -o disk.img -b boot.bin \
+		kernel 0x10000 user.img 0x40000
 
-.PHONY:	all clean realclean
diff --git a/boot/boot.ld b/boot/boot.ld
new file mode 100644
index 0000000..e6fc072
--- /dev/null
+++ b/boot/boot.ld
@@ -0,0 +1,9 @@
+OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
+OUTPUT_ARCH(i386)
+ENTRY(bootentry)
+
+SECTIONS
+{
+	. = 0x0;
+	.text : { *(.text .stub .text.*) }
+}
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..0485259
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,351 @@
+const std = @import("std");
+const builtin = @import("builtin");
+
+const c_flags = &[_][]const u8{
+    // lang
+    "-std=c99",
+    // warnings
+    "-Wall",
+    "-Wextra",
+    "-pedantic",
+    // flags
+    "-fno-pie",
+    "-fno-stack-protector",
+    "-fno-omit-frame-pointer",
+    "-ffreestanding",
+    "-fno-builtin",
+};
+
+const ld_flags = &[_][]const u8{
+    "-nmagic",
+    "-nostdlib",
+    "--no-warn-rwx-segments",
+};
+
+const boot_src = &[_][]const u8{"boot/boot.S"};
+
+const kernel_src = &[_][]const u8{
+    "kernel/cio.c",
+    "kernel/clock.c",
+    "kernel/isrs.S",
+    "kernel/kernel.c",
+    "kernel/kmem.c",
+    "kernel/list.c",
+    "kernel/procs.c",
+    "kernel/sio.c",
+    "kernel/startup.S",
+    "kernel/support.c",
+    "kernel/syscalls.c",
+    "kernel/user.c",
+    "kernel/vm.c",
+    "kernel/vmtables.c",
+    "lib/klibc.c",
+};
+
+const lib_src = &[_][]const u8{
+    "lib/bound.c",
+    "lib/cvtdec0.c",
+    "lib/cvtdec.c",
+    "lib/cvthex.c",
+    "lib/cvtoct.c",
+    "lib/cvtuns0.c",
+    "lib/cvtuns.c",
+    "lib/memclr.c",
+    "lib/memcpy.c",
+    "lib/memset.c",
+    "lib/pad.c",
+    "lib/padstr.c",
+    "lib/sprint.c",
+    "lib/str2int.c",
+    "lib/strcat.c",
+    "lib/strcmp.c",
+    "lib/strcpy.c",
+    "lib/strlen.c",
+};
+
+const ulib_src = &[_][]const u8{
+    "lib/entry.S",
+    "lib/ulibc.c",
+    "lib/ulibs.S",
+};
+
+const Prog = struct {
+    name: []const u8,
+    source: []const []const u8,
+};
+
+const util_progs = &[_]Prog{
+    // mkblob
+    Prog{
+        .name = "mkblob",
+        .source = &[_][]const u8{"util/mkblob.c"},
+    },
+    // listblob
+    Prog{
+        .name = "listblob",
+        .source = &[_][]const u8{"util/listblob.c"},
+    },
+    // BuildImage
+    Prog{
+        .name = "BuildImage",
+        .source = &[_][]const u8{"util/BuildImage.c"},
+    },
+};
+
+const user_progs = &[_]Prog{
+    // idle
+    Prog{
+        .name = "idle",
+        .source = &[_][]const u8{"user/idle.c"},
+    },
+    // init
+    Prog{
+        .name = "init",
+        .source = &[_][]const u8{"user/init.c"},
+    },
+    // progABC
+    Prog{
+        .name = "progABC",
+        .source = &[_][]const u8{"user/progABC.c"},
+    },
+    // progDE
+    Prog{
+        .name = "progDE",
+        .source = &[_][]const u8{"user/progDE.c"},
+    },
+    // progFG
+    Prog{
+        .name = "progFG",
+        .source = &[_][]const u8{"user/progFG.c"},
+    },
+    // progH
+    Prog{
+        .name = "progH",
+        .source = &[_][]const u8{"user/progH.c"},
+    },
+    // progI
+    Prog{
+        .name = "progI",
+        .source = &[_][]const u8{"user/progI.c"},
+    },
+    // progJ
+    Prog{
+        .name = "progJ",
+        .source = &[_][]const u8{"user/progJ.c"},
+    },
+    // progKL
+    Prog{
+        .name = "progKL",
+        .source = &[_][]const u8{"user/progKL.c"},
+    },
+    // progKL
+    Prog{
+        .name = "progKL",
+        .source = &[_][]const u8{"user/progKL.c"},
+    },
+    // progMN
+    Prog{
+        .name = "progMN",
+        .source = &[_][]const u8{"user/progMN.c"},
+    },
+    // progP
+    Prog{
+        .name = "progP",
+        .source = &[_][]const u8{"user/progP.c"},
+    },
+    // progQ
+    Prog{
+        .name = "progQ",
+        .source = &[_][]const u8{"user/progQ.c"},
+    },
+    // progR
+    Prog{
+        .name = "progR",
+        .source = &[_][]const u8{"user/progR.c"},
+    },
+    // progS
+    Prog{
+        .name = "progS",
+        .source = &[_][]const u8{"user/progS.c"},
+    },
+    // progTUV
+    Prog{
+        .name = "progTUV",
+        .source = &[_][]const u8{"user/progTUV.c"},
+    },
+    // progW
+    Prog{
+        .name = "progW",
+        .source = &[_][]const u8{"user/progW.c"},
+    },
+    // progX
+    Prog{
+        .name = "progX",
+        .source = &[_][]const u8{"user/progX.c"},
+    },
+    // progY
+    Prog{
+        .name = "progY",
+        .source = &[_][]const u8{"user/progY.c"},
+    },
+    // progZ
+    Prog{
+        .name = "progZ",
+        .source = &[_][]const u8{"user/progZ.c"},
+    },
+    // shell
+    Prog{
+        .name = "shell",
+        .source = &[_][]const u8{"user/shell.c"},
+    },
+};
+
+const AddSourcesOpts = struct { exe: *std.Build.Step.Compile, sources: []const []const []const u8, c_flags: []const []const u8 };
+
+fn add_sources(b: *std.Build, opts: AddSourcesOpts) void {
+    // add asm and c source files
+    for (opts.sources) |source| {
+        for (source) |file| {
+            if (std.mem.endsWith(u8, file, ".c")) {
+                // c file
+                opts.exe.addCSourceFile(.{ .file = b.path(file), .flags = opts.c_flags });
+            } else {
+                // assembly file
+                opts.exe.addAssemblyFile(b.path(file));
+            }
+        }
+    }
+}
+
+const BuildKernBinaryOpts = struct {
+    name: []const u8,
+    target: std.Build.ResolvedTarget,
+    optimize: std.builtin.OptimizeMode,
+    sources: []const []const []const u8,
+    linker: ?[]const u8 = null,
+    entry: []const u8 = "_start",
+};
+
+fn build_kern_binary(b: *std.Build, opts: BuildKernBinaryOpts) void {
+    // create compile step
+    const exe = b.addExecutable(.{
+        .name = opts.name,
+        .target = opts.target,
+        .optimize = opts.optimize,
+        .strip = true,
+    });
+
+    // add include path
+    exe.addIncludePath(b.path("include/"));
+    exe.entry = .{ .symbol_name = opts.entry };
+
+    // add asm and c source files
+    add_sources(b, .{
+        .exe = exe,
+        .sources = opts.sources,
+        .c_flags = c_flags,
+    });
+
+    if (opts.linker != null) {
+        exe.setLinkerScript(b.path(opts.linker.?));
+    }
+
+    const step = b.addInstallArtifact(exe, .{ .dest_dir = .{
+        .override = .{ .custom = "../bin" },
+    } });
+    b.getInstallStep().dependOn(&step.step);
+}
+
+const BuildNativeBinaryOpts = struct {
+    name: []const u8,
+    optimize: std.builtin.OptimizeMode,
+    sources: []const []const []const u8,
+};
+
+fn build_native_binary(b: *std.Build, opts: BuildNativeBinaryOpts) void {
+    // create compile step
+    const exe = b.addExecutable(.{
+        .name = opts.name,
+        .target = b.graph.host,
+        .optimize = opts.optimize,
+        .strip = true,
+    });
+
+    // include libc
+    exe.linkLibC();
+
+    // add asm and c source files
+    add_sources(b, .{
+        .exe = exe,
+        .sources = opts.sources,
+        .c_flags = &.{},
+    });
+
+    const step = b.addInstallArtifact(exe, .{ .dest_dir = .{
+        .override = .{ .custom = "../bin" },
+    } });
+    b.getInstallStep().dependOn(&step.step);
+}
+
+pub fn build(b: *std.Build) !void {
+
+    // context
+    const target = b.standardTargetOptions(.{
+        .default_target = .{
+            .cpu_arch = std.Target.Cpu.Arch.x86,
+            .os_tag = std.Target.Os.Tag.freestanding,
+            .abi = std.Target.Abi.gnu,
+            .ofmt = std.Target.ObjectFormat.elf,
+        },
+    });
+    const optimize = std.builtin.OptimizeMode.ReleaseSmall;
+
+    // boot
+    build_kern_binary(b, .{
+        .name = "boot",
+        .target = target,
+        .optimize = optimize,
+        .sources = &.{
+            boot_src,
+        },
+        .linker = "boot/boot.ld",
+        .entry = "bootentry",
+    });
+
+    // kernel
+    build_kern_binary(b, .{
+        .name = "kernel",
+        .target = target,
+        .optimize = optimize,
+        .sources = &.{
+            kernel_src,
+            lib_src,
+        },
+        .linker = "kernel/kernel.ld",
+    });
+
+    // user_progs
+    for (user_progs) |prog| {
+        build_kern_binary(b, .{
+            .name = prog.name,
+            .target = target,
+            .optimize = optimize,
+            .sources = &.{
+                prog.source,
+                lib_src,
+                ulib_src,
+            },
+        });
+    }
+
+    // util_progs
+    for (util_progs) |prog| {
+        build_native_binary(b, .{
+            .name = prog.name,
+            .optimize = optimize,
+            .sources = &.{
+                prog.source,
+            },
+        });
+    }
+}
diff --git a/kernel/sio.c b/kernel/sio.c
index a5c7b75..fc20435 100644
--- a/kernel/sio.c
+++ b/kernel/sio.c
@@ -106,7 +106,7 @@ static uint8_t ier;
 
 // queue for read-blocked processes
 #ifdef QNAME
-QTYPE QNAME;
+extern QTYPE QNAME;
 #endif
 
 /*
@@ -253,7 +253,7 @@ static void sio_isr( int vector, int ecode ) {
 			sprint( b256, "sio isr: IIR %02x\n", ((uint32_t) iir) & 0xff );
 			PANIC( 0, b256 );
 		}
-	
+
 	}
 
 	// should never reach this point!
@@ -329,7 +329,7 @@ void sio_init( void ) {
 	*/
 
 	outb( UA4_LCR, UA4_LCR_WLS_8 | UA4_LCR_1_STOP_BIT | UA4_LCR_NO_PARITY );
-	
+
 	/*
 	** Set the ISEN bit to enable the interrupt request signal,
 	** and the DTR and RTS bits to enable two-way communication.
@@ -450,7 +450,7 @@ int sio_readc( void ) {
 	// assume there is no character available
 	ch = -1;
 
-	// 
+	//
 	// If there is a character, return it
 	//
 
@@ -675,7 +675,7 @@ void sio_dump( bool_t full ) {
 
 	if( incount ) {
 		cio_puts( "SIO input queue: \"" );
-		ptr = innext; 
+		ptr = innext;
 		for( n = 0; n < incount; ++n ) {
 			put_char_or_code( *ptr++ );
 		}
@@ -685,7 +685,7 @@ void sio_dump( bool_t full ) {
 	if( outcount ) {
 		cio_puts( "SIO output queue: \"" );
 		cio_puts( " ot: \"" );
-		ptr = outnext; 
+		ptr = outnext;
 		for( n = 0; n < outcount; ++n )  {
 			put_char_or_code( *ptr++ );
 		}