diff --git a/build.lua b/build.lua
index c01435a83..b446dbd5e 100644
--- a/build.lua
+++ b/build.lua
@@ -3,6 +3,7 @@ installable {
 	map = {
 		"util/ack+ack-pkg",
 		"util/amisc+pkg",
+		"lang/cem/cemcom.ansi+pkg",
 	}
 }
 
diff --git a/lang/cem/cemcom.ansi/build.lua b/lang/cem/cemcom.ansi/build.lua
new file mode 100644
index 000000000..8dfd6ab7c
--- /dev/null
+++ b/lang/cem/cemcom.ansi/build.lua
@@ -0,0 +1,254 @@
+local posix = require("posix")
+include("util/LLgen/build.lua")
+
+normalrule {
+	name = "parameters",
+	ins = { "./BigPars" },
+	outleaves = { "parameters.h" },
+	commands = {
+		"echo '#ifndef PARAMETERS_H' > %{outs}",
+		"echo '#define PARAMETERS_H' >> %{outs}",
+		"grep -v '^!' < %{ins} >> %{outs}",
+		"echo '#endif' >> %{outs}"
+	}
+}
+
+local str_files = basename(posix.glob("lang/cem/cemcom.ansi/*.str"))
+local str_targets = {}
+
+for _, f in ipairs(str_files) do
+	local bf = f:gsub("%.str$", "")
+	str_targets[#str_targets+1] = normalrule {
+		name = "allocd_header/"..bf,
+		ins = { "./make.allocd", "./"..f },
+		outleaves = { bf..".h" },
+		commands = {
+			"%{ins[1]} < %{ins[2]} > %{outs}"
+		}
+	}
+end
+
+normalrule {
+	name = "next-c",
+	ins = {
+		"./make.next",
+		"./*.str",
+	},
+	outleaves = { "next.c" },
+	commands = {
+		"%{ins} > %{outs[1]}"
+	}
+}
+
+clibrary {
+	name = "nextlib",
+	srcs = { "+next-c" },
+	hdrs = str_targets,
+	deps = {
+		"+parameters",
+		unpack(str_targets)
+	}
+}
+
+normalrule {
+	name = "tokenname-g",
+	ins = {
+		"./make.tokfile",
+		"./tokenname.c",
+	},
+	outleaves = { "tokenname.g" },
+	commands = {
+		"sh %{ins[1]} < %{ins[2]} > %{outs}",
+	}
+}
+
+llgen {
+	name = "llgen",
+	srcs = {
+		"+tokenname-g", -- must be first
+		"./*.g",
+	},
+}
+
+cprogram {
+	name = "cemcom",
+	srcs = {
+		"./ch3.c",
+		"./ch3bin.c",
+		"./ch3mon.c",
+		"./code.c",
+		"./conversion.c",
+		"./cstoper.c",
+		"./dataflow.c",
+		"./declarator.c",
+		"./decspecs.c",
+		"./domacro.c",
+		"./dumpidf.c",
+		"./error.c",
+		"./eval.c",
+		"./expr.c",
+		"./field.c",
+		"./fltcstoper.c",
+		"./idf.c",
+		"./init.c",
+		"./input.c",
+		"./l_comment.c",
+		"./l_ev_ord.c",
+		"./l_lint.c",
+		"./l_misc.c",
+		"./l_outdef.c",
+		"./l_states.c",
+		"./label.c",
+		"./main.c",
+		"./options.c",
+		"./pragma.c",
+		"./proto.c",
+		"./replace.c",
+		"./skip.c",
+		"./stab.c",
+		"./stack.c",
+		"./struct.c",
+		"./switch.c",
+		"./tokenname.c",
+		"./type.c",
+		"./util.c",
+	},
+	deps = {
+		"+parameters",
+		"+nextlib",
+		"+llgen",
+		"h+emheaders",
+		"modules+alloc",
+		"modules+flt_arith",
+		"modules+idf",
+		"modules+headers",
+		"modules+system",
+	},
+}
+
+installable {
+	name = "pkg",
+	map = {
+		["$(PLATDEP)/em_cemcom.ansi"] = "+cemcom"
+	}
+}
+--[[
+
+D := lang/cem/cemcom.ansi
+
+define build-cemcom-ansi-allocd-header
+$(eval g := $(OBJDIR)/$D/$(strip $1).h)
+$g: $D/$(strip $1).str $D/make.allocd
+	@echo ALLOCD $$@
+	@mkdir -p $$(dir $$@)
+	$(hide) $D/make.allocd < $$^ > $$@
+
+$(eval CLEANABLES += $g)
+$(eval $q: $g)
+endef
+
+define build-cemcom-ansi-next
+$(eval CLEANABLES += $(OBJDIR)/$D/next.c)
+$(OBJDIR)/$D/next.c: $D/make.next $1
+	@echo NEXT $$@
+	@mkdir -p $$(dir $$@)
+	$(hide) $$^ > $$@
+$(call cfile, $(OBJDIR)/$D/next.c)
+
+$(foreach f, $1, $(call build-cemcom-ansi-allocd-header, \
+	$(basename $(notdir $f))))
+endef
+
+define build-cemcom-ansi-impl
+
+$(call reset)
+$(eval cflags += -I$(OBJDIR)/$D -I$D)
+
+$(call cfile, $D/arith.c)
+$(call dependson, $(INCDIR)/flt_arith.h)
+
+$(call cfile, $D/blocks.c)
+$(call dependson, $(INCDIR)/em_codeEK.h)
+
+$(call cfile, $D/LLlex.c)
+$(call cfile, $D/LLmessage.c)
+
+
+$(call llgen, $(OBJDIR)/$D, \
+	$(OBJDIR)/$D/tokenfile.g \
+	$D/program.g \
+	$D/declar.g \
+	$D/expression.g \
+	$D/statement.g \
+	$D/ival.g)
+
+$(eval CLEANABLES += $(OBJDIR)/$D/tokenfile.g)
+$(OBJDIR)/$D/tokenfile.g: $D/make.tokfile $D/tokenname.c
+	@echo TOKENFILE $$@
+	@mkdir -p $$(dir $$@)
+	$(hide) sh $D/make.tokfile < $D/tokenname.c > $$@
+
+$(call tabgen, $D/char.tab)
+
+$(eval $q: $(OBJDIR)/$D/parameters.h)
+
+$(eval CLEANABLES += $(OBJDIR)/$D/parameters.h)
+$(OBJDIR)/$D/parameters.h: $D/BigPars
+	@echo PARAMETERS $$@
+	@mkdir -p $$(dir $$@)
+	$(hide) echo '#ifndef PARAMETERS_H' > $$@
+	$(hide) echo '#define PARAMETERS_H' >> $$@
+	$(hide) grep -v '^!' < $D/BigPars >> $$@
+	$(hide) echo '#endif' >> $$@
+
+$(eval CLEANABLES += $(OBJDIR)/$D/symbol2str.c)
+$(OBJDIR)/$D/symbol2str.c: $D/make.tokcase $D/tokenname.c
+	@echo TOKCASE $$@
+	@mkdir -p $$(dir $$@)
+	$(hide) $D/make.tokcase < $D/tokenname.c > $$@
+$(call cfile, $(OBJDIR)/$D/symbol2str.c)
+
+$(call build-cemcom-ansi-next, \
+		$D/code.str \
+		$D/declar.str \
+		$D/def.str \
+		$D/expr.str \
+		$D/field.str \
+		$D/estack.str \
+		$D/util.str \
+		$D/proto.str \
+		$D/replace.str \
+		$D/idf.str \
+		$D/macro.str \
+		$D/stack.str \
+		$D/stmt.str \
+		$D/struct.str \
+		$D/switch.str \
+		$D/type.str \
+		$D/l_brace.str \
+		$D/l_state.str \
+		$D/l_outdef.str)
+
+$(eval $q: $(OBJDIR)/$D/Lpars.h)
+
+$(call rawfile, $(LIBEM_MES))
+$(call rawfile, $(LIBEMK))
+$(call rawfile, $(LIBEM_DATA))
+$(call rawfile, $(LIBINPUT))
+$(call rawfile, $(LIBASSERT))
+$(call rawfile, $(LIBALLOC))
+$(call rawfile, $(LIBFLT_ARITH))
+$(call rawfile, $(LIBPRINT))
+$(call rawfile, $(LIBSYSTEM))
+$(call rawfile, $(LIBSTRING))
+$(call cprogram, $(BINDIR)/cemcom.ansi)
+$(call installto, $(PLATDEP)/em_cemcom.ansi)
+$(eval CEMCOMANSI := $o)
+
+$(call reset)
+$(eval q := $D/cemcom.ansi.1)
+$(call installto, $(INSDIR)/share/man/man1/cemcom.6)
+endef
+
+$(eval $(build-cemcom-ansi-impl))
+--]]
diff --git a/lang/cem/cemcom.ansi/char.tab b/lang/cem/cemcom.ansi/char.tab
index 329178284..809f1c6ce 100644
--- a/lang/cem/cemcom.ansi/char.tab
+++ b/lang/cem/cemcom.ansi/char.tab
@@ -1,4 +1,6 @@
 %
+hdrs = { "./src/system/system.h" },
+hdrs = { "./src/system/system.h" },
 %	CHARACTER CLASSES
 %
 % some general settings:
diff --git a/modules/build.lua b/modules/build.lua
index 40a9d1737..18a380f20 100644
--- a/modules/build.lua
+++ b/modules/build.lua
@@ -3,6 +3,38 @@ bundle {
 	srcs = { "./h/*.h" }
 }
 
+clibrary {
+	name = "system",
+	srcs = { "./src/system/*.c" },
+	hdrs = { "./src/system/system.h" },
+	deps = { "+headers" },
+}
+
+clibrary {
+	name = "alloc",
+	srcs = { "./src/alloc/*.c" },
+	hdrs = { "./src/alloc/alloc.h" },
+	deps = {
+		"+headers",
+		"+system"
+	},
+}
+
+clibrary {
+	name = "flt_arith",
+	srcs = { "./src/flt_arith/*.c" },
+	hdrs = { "./src/flt_arith/flt_arith.h" },
+	deps = {
+		"+headers"
+	}
+}
+
+clibrary {
+	name = "idf",
+	srcs = {},
+	hdrs = { "./src/idf/idf_pkg.*" },
+}
+
 clibrary {
 	name = "string",
 	srcs = { "./src/string/*.c" },
diff --git a/util/LLgen/build.lua b/util/LLgen/build.lua
new file mode 100644
index 000000000..0a2c29f6f
--- /dev/null
+++ b/util/LLgen/build.lua
@@ -0,0 +1,97 @@
+cprogram {
+	name = "llgen",
+
+	-- These use pre-LLgen'd versions of LLgen.c, Lpars.c and tokens.c. If
+	-- LLgen.g gets updated, they need rebuilding. Use the bootstrap script to
+	-- do this.
+
+	srcs = { "./src/*.c" },
+	cflags = { "-DLIBDIR=\\\""..abspath("util/LLgen/lib").."\\\"", "-DNON_CORRECTING" },
+}
+
+definerule("llgen",
+	{
+		srcs = { type="targets" },
+	},
+	function(e)
+		-- Remember this is executed from the caller's directory; local
+		-- target names will resolve there
+		local fs = replace(basename(e.srcs), "%.g$", "")
+
+		return normalrule {
+			name = e.name,
+			cwd = e.cwd,
+			outleaves = {
+				"Lpars.c",
+				"Lpars.h",
+				unpack(replace(fs, "$", ".c"))
+			},
+			ins = {
+				"util/LLgen+llgen",
+				unpack(e.srcs),
+			},
+			commands = {
+				"cd %{dir} && %{abspath(ins)}"
+			}
+		}
+	end
+)
+
+--[[
+D := util/LLgen
+
+# Rule to build LLgen.
+
+define build-llgen-impl
+	$(call reset)
+	$(eval cflags += -DNON_CORRECTING -DLIBDIR=\"$(abspath $D/lib)\")
+	$(call cfile, $D/src/main.c)
+	$(call cfile, $D/src/gencode.c)
+	$(call cfile, $D/src/compute.c)
+	$(call cfile, $D/src/check.c)
+	$(call cfile, $D/src/reach.c)
+	$(call cfile, $D/src/global.c)
+	$(call cfile, $D/src/name.c)
+	$(call cfile, $D/src/sets.c)
+	$(call cfile, $D/src/alloc.c)
+	$(call cfile, $D/src/machdep.c)
+	$(call cfile, $D/src/cclass.c)
+	$(call cfile, $D/src/savegram.c)
+
+	# These use pre-LLgen'd version of the files. If LLgen.g gets updated,
+	# they need rebuilding. Use the bootstrap script to do this.
+
+	$(call cfile, $D/src/LLgen.c)
+	$(call cfile, $D/src/Lpars.c)
+	$(call cfile, $D/src/tokens.c)
+
+	$(call cprogram, $(BINDIR)/LLgen)
+	LLGEN := $o
+endef
+
+$(eval $(build-llgen-impl))
+
+# Rule to invoke to *use* LLgen.
+#
+#  $1: directory to put output files
+#  $2: input files
+#
+# Output files are compiled via cfile and queued.
+
+define llgen-impl
+$(eval o := $1/Lpars.c $(patsubst %.g, $(strip $1)/%.c, $(notdir $2)))
+$(eval CLEANABLES += $o $1/Lpars.h)
+
+$o: $1/Lpars.h
+$1/Lpars.h: $2 $(LLGEN)
+	@echo LLGEN $1/Lpars.c
+	@mkdir -p $1
+	$(hide) $(RM) $o $1/Lpars.h
+	$(hide) cd $1 && $(LLGEN) $(abspath $2)
+
+$(foreach f,$o,$(call cfile,$f))
+
+endef
+
+llgen = $(eval $(call llgen-impl,$1,$2))
+--]]