GenerateContext struct #RefType {
	comp Compilation
	units List<CodeUnit>
	errors List<Error>
	top Namespace
	tags CommonTags
	out CodeBuilder
	nextLocal int
	nextLabel int
	variantTagArgs Array<Tag>
	function FunctionDef
	infoMap Map<Node, NodeInfo>
	breakLabel string
	breakCount int
	continueLabel string
	continueCount int
	aliasMap Map<string, string>
	currentExpressionStatement ExpressionStatement
	emitUnreachable bool
}

CodeBuilder struct #RefType {
	sb StringBuilder
	indentLevel int
	
	writeLine(this CodeBuilder, s string) {
		for i := 0; i < this.indentLevel {
			this.sb.writeChar('\t')
		}
		this.sb.write(s)
		this.sb.write("\n")
	}
	
	indent(this CodeBuilder) {
		this.indentLevel += 1
	}
	
	unIndent(this CodeBuilder) {
		this.indentLevel -= 1
	}

	toString(this CodeBuilder) {
		return this.sb.toString()
	}
}

CGenerator {
	createContext(comp Compilation) {
		return GenerateContext {
			comp: comp, 
			units: comp.units,
			errors: comp.errors,
			top: comp.top,
			tags: comp.tags,		
			out: new CodeBuilder { sb: new StringBuilder{} },
			aliasMap: new Map.create<string, string>(),
		}
	}
	
	generate(c GenerateContext, includeFile string, entryPoint bool, compilerVersion string) {
		c.out.writeLine(format("// Generated by Muon compiler, version {}", compilerVersion))
		c.out.writeLine(format("#include \"{}\"", includeFile))
		c.out.writeLine("// GENERAL DEFS")
		c.out.writeLine("#ifdef _MSC_VER // for MSVC compatibility")
		c.out.writeLine("#define _Thread_local __declspec(thread)")
		c.out.writeLine("#define _Static_assert static_assert")
		c.out.writeLine("#endif")
		if (c.comp.flags & CompilationFlags.target32bit) == 0 {
			c.out.writeLine("_Static_assert(sizeof(uintptr_t) == 8, \"Must use 64-bit output target\");")
		} else {
			c.out.writeLine("_Static_assert(sizeof(uintptr_t) == 4, \"Must use 32-bit output target\");")
		}
		c.out.writeLine("typedef void void__;")
		c.out.writeLine("typedef int8_t sbyte__;")
		c.out.writeLine("typedef uint8_t byte__;")
		c.out.writeLine("typedef int16_t short__;")
		c.out.writeLine("typedef uint16_t ushort__;")
		c.out.writeLine("typedef int32_t int__;")
		c.out.writeLine("typedef uint32_t uint__;")
		c.out.writeLine("typedef int64_t long__;")
		c.out.writeLine("typedef uint64_t ulong__;")
		if (c.comp.flags & CompilationFlags.target32bit) == 0 {
			c.out.writeLine("typedef int64_t ssize__;")
			c.out.writeLine("typedef uint64_t usize__;")
		} else {
			c.out.writeLine("typedef int32_t ssize__;")
			c.out.writeLine("typedef uint32_t usize__;")
		}
		c.out.writeLine("#ifdef _MSC_VER")
		c.out.writeLine("typedef struct __declspec(align(16)) tag_____s128__ { uint64_t lo; int64_t hi; } s128__;")
		c.out.writeLine("#else")
		c.out.writeLine("typedef __int128 s128__;")
		c.out.writeLine("#endif")
		c.out.writeLine("typedef float float__;")
		c.out.writeLine("typedef double double__;")
		c.out.writeLine("typedef uint8_t bool__;")
		c.out.writeLine("typedef uint32_t bool32__;")
		c.out.writeLine("typedef char char__;")
		c.out.writeLine("typedef uint8_t* pointer__;")
		c.out.writeLine("typedef struct { pointer__ dataPtr__; int__ length__; } string__;")
		c.out.writeLine("typedef char* cstring__;")
		c.out.writeLine("#define null__ NULL")
		c.out.writeLine("#define false__ 0")
		c.out.writeLine("#define true__ 1")
		c.out.writeLine("#define mu_____abandon(x) { top_____abandonFn__(x); }")
		c.out.writeLine("#define mu_____assert(x) { if (!(x)) { top_____abandonFn__(-2); } }")
		c.out.writeLine("#define mu_____boundscheck(x) { if (!(x)) { top_____abandonFn__(-3); } }")
		c.out.writeLine("#define mu_____checkedcast(x) { if (!(x)) { top_____abandonFn__(-4); } }")
		c.out.writeLine("#define mu_____nomatch() { top_____abandonFn__(-5); }")
		c.out.writeLine("// STRING TABLE")
		c.out.writeLine("string__ mu_____string0 = { (pointer__)\"\", 0 };")
		sb := new StringBuilder{}
		for e in c.comp.strings {
			sb.write(format("string__ mu_____string{} = {{ (pointer__)\"", e.value))
			writeUnescapedString(sb, e.key)
			sb.write(format("\", {} }};", e.key.length))
			c.out.writeLine(sb.toString())
			sb.clear()
		}		
		c.out.writeLine("// FORWARD TYPES")
		forwardPass(c)
		c.out.writeLine("// STRUCTS")
		sdefPass(c)
		c.out.writeLine("// STATIC FIELDS")
		if (c.comp.flags & CompilationFlags.useArgcArgv) != 0 {
			c.out.writeLine("static int__ mu_____argc;")
			c.out.writeLine("static pointer__ mu_____argv;")
		}
		sfNamespace(c, c.top)
		c.out.writeLine("// FUNCTIONS")
		forwardFunNamespace(c, c.top)
		funNamespace(c, c.top)
		if entryPoint {
			c.out.writeLine("// ENTRYPOINT")
			if (c.comp.flags & CompilationFlags.useArgcArgv) != 0 {
				c.out.writeLine("int main(int argc, char** argv) {")
				c.out.indent()
				c.out.writeLine("mu_____argc = argc;")
				c.out.writeLine("mu_____argv = (pointer__)argv;")
			} else {
				c.out.writeLine("int main() {")
				c.out.indent()
			}		
			c.out.writeLine("top_____main__();")
			c.out.writeLine("return 0;")
			c.out.unIndent()
			c.out.writeLine("}")
		}
	}
	
	forwardPass(c GenerateContext) {
		forwardNamespace(c, c.top)
		for ta in c.tags.funTi.tas {		
			sb := StringBuilder{}
			sb.write("typedef ")
			writeTag(ref sb, ta[ta.count - 1])
			sb.write(" (*")
			writeTag(ref sb, Tag { ti: c.tags.funTi, args: ta })
			sb.write(")(")
			insertSep := false
			for i := 0; i < ta.count - 1 {
				if insertSep {
					sb.write(", ")
				} else {
					insertSep = true
				}
				writeTag(ref sb, ta[i])
			}
			sb.write(");")
			c.out.writeLine(sb.toString())
		}
	}
	
	forwardNamespace(c GenerateContext, ns Namespace) {
		if (ns.flags & (TypeFlags.struct_ | TypeFlags.string_)) == TypeFlags.struct_ {
			if ns.tas != null {
				for ta in ns.tas {
					forwardStructVariant(c, Tag { ti: ns, args: ta })
				}
				ns.tasGenerated = new Tag.createArgsSet()
			} else {
				forwardStructVariant(c, Tag { ti: ns })
			}
		} else if (ns.flags & TypeFlags.enum_) != 0 {
			c.out.writeLine(format("typedef uint32_t {};", symNs(ns)))
		} else if (ns.flags & TypeFlags.taggedPointerEnum) != 0 {
			sym := symNs(ns)
			c.out.writeLine(format("typedef struct {{ int__ id__; pointer__ dataPtr__; }} {};", sym))
			id := 1
			for ns.taggedPointerOptions {
				c.out.writeLine(format("#define {}___{} {}", sym, symNestedTag(it), id))
				id += 1
			}
		}
		for e in ns.members {
			mem := e.value
			match mem {
				Namespace: forwardNamespace(c, mem)
				default: {}
			}
		}
	}
	
	forwardStructVariant(c GenerateContext, tag Tag) {
		sym := symTag(tag)
		c.out.writeLine(format("typedef struct tag_____{} {};", sym, sym))
	}
		
	sdefPass(c GenerateContext) {
		sdefNamespace(c, c.top)
	}
	
	sdefNamespace(c GenerateContext, ns Namespace) {
		if (ns.flags & (TypeFlags.struct_ | TypeFlags.string_ | TypeFlags.generated)) == TypeFlags.struct_ {
			if ns.tas != null {
				for ta in ns.tas {
					if !ns.tasGenerated.contains(ta) {
						sdefStructVariant(c, Tag { ti: ns, args: ta })
					}
				}
			} else {
				sdefStructVariant(c, Tag { ti: ns })
			}
		}
		for e in ns.members {
			mem := e.value
			match mem {
				Namespace: sdefNamespace(c, mem)
				default: {}
			}
		}
	}
	
	sdefStructVariant(c GenerateContext, tag Tag) {
		for e in tag.ti.members {
			mem := e.value
			match mem {
				FieldDef: {				
					ftag := TypeChecker.closeTag(mem.tag, tag.ti.typeParamList, tag.args)
					if (ftag.ti.flags & (TypeFlags.struct_ | TypeFlags.string_ | TypeFlags.generated)) == TypeFlags.struct_ {
						if ftag.ti.tas != null {							
							if !ftag.ti.tasGenerated.contains(ftag.args) {
								sdefStructVariant(c, ftag)								
							}
						} else {							
							sdefStructVariant(c, ftag)
						}
					}
				}
				default: {}
			}
		}
		sym := symTag(tag)
		c.out.writeLine(format("typedef struct tag_____{} {{", sym))
		c.out.indent()
		for fd in tag.ti.fields {
			ftag := TypeChecker.closeTag(fd.tag, tag.ti.typeParamList, tag.args)
			c.out.writeLine(format("{} {};", symTag(ftag), symId(fd.name)))
		}
		c.out.unIndent()
		c.out.writeLine(format("}} {};", sym))
		if tag.ti.tas != null {
			tag.ti.tasGenerated.add(tag.args)
			if tag.ti.tasGenerated.count == tag.ti.tas.count {
				tag.ti.flags |= TypeFlags.generated
			}
		} else {
			tag.ti.flags |= TypeFlags.generated
		}			
	}
	
	sfNamespace(c GenerateContext, ns Namespace) {
		for e in ns.members {
			mem := e.value
			match mem {
				Namespace: sfNamespace(c, mem)
				StaticFieldDef: sfStaticField(c, mem)
				default: {}
			}
		}
	}
	
	sfStaticField(c GenerateContext, sf StaticFieldDef) {
		c.infoMap = sf.infoMap
		sb := new StringBuilder{}
		Interpreter.computeStaticField(c, sf)
		er := sf.evaluatedValue
		if er.type == EvalResultType.value || er.type == EvalResultType.defaultValue || er.type == EvalResultType.generateVar {
			if (sf.flags & StaticFieldFlags.threadLocal) != 0 {
				sb.write("_Thread_local")
			} else if (sf.flags & StaticFieldFlags.mutable) != 0 {
				sb.write("static")
			} else {
				sb.write("const")
			}
			sb.write(" ")
			tag := sf.tag
			if (tag.ti.flags & TypeFlags.enum_) != 0 {
				tag = c.tags.uint_
			}
			writeTag(sb, tag)
			sb.write(" ")
			writeStaticField(sb, sf)
			if er.type == EvalResultType.value {
				sb.write(" = ")
				assert((tag.ti.flags & (TypeFlags.intval | TypeFlags.boolval)) != 0)
				genOpaqueValue(c, tag, er.opaqueValue, sb)
			} else if er.type == EvalResultType.generateVar {
				sb.write(" = ")
				genExpression(c, sf.initializeExpr, sb)
			}
			sb.write(";")
			c.out.writeLine(sb.toString())
		} else if er.type == EvalResultType.generateDefine {
			sb.write("#define ")
			writeStaticField(sb, sf)
			sb.write(" ")
			genExpression(c, sf.initializeExpr, sb)
			c.out.writeLine(sb.toString())
		} else if er.type == EvalResultType.generateForeign {
			sb.write("#define ")
			writeStaticField(sb, sf)
			sb.write(" (")
			writeTag(sb, sf.tag)
			sb.write(")(")
			sb.write(sf.foreignName)
			sb.write(")")
			c.out.writeLine(sb.toString())
		} else {
			badInitializeExpr(c, sf)
		}
	}
	
	badInitializeExpr(c GenerateContext, sf StaticFieldDef) {
		c.errors.add(Error.at(sf.unit, RangeFinder.find(sf.initializeExpr), "Expression cannot be evaluated at compile time (this will be improved in the future)"))
	}

	forwardFunNamespace(c GenerateContext, ns Namespace) {
		for e in ns.members {
			mem := e.value
			match mem {
				Namespace: forwardFunNamespace(c, mem)
				FunctionDef: {
					fd := mem
					if (fd.flags & FunctionFlags.foreign) == 0 {
						if fd.tas != null {
							for ta in fd.tas {
								forwardFunVariant(c, fd, ta)
							}
						} else {
							forwardFunVariant(c, fd, null)
						}				
					}
				}
				default: {}
			}
		}
	}
	
	forwardFunVariant(c GenerateContext, fd FunctionDef, ta Array<Tag>) {
		sb := StringBuilder{}
		writeFunVariantSignature(ref sb, fd, ta)
		sb.write(";")
		c.out.writeLine(sb.toString())
	}
	
	writeFunVariantSignature(sb StringBuilder, fd FunctionDef, ta Array<Tag>) {
		writeTag(sb, TypeChecker.closeTag(fd.returnTag, fd.typeParamList, ta))
		sb.write(" ")
		if (fd.flags & FunctionFlags.callingConvention) != 0 {
			sb.write(fd.callingConvention)
			sb.write(" ")
		}
		writeFunVariant(sb, fd, ta)
		sb.write("(")
		insertSep := false
		for p in fd.params {
			if insertSep {
				sb.write(", ")
			} else {
				insertSep = true
			}
			writeTag(sb, TypeChecker.closeTag(p.tag, fd.typeParamList, ta))
			sb.write(" ")
			writeId(sb, p.name)
		}
		sb.write(")")
	}
	
	funNamespace(c GenerateContext, ns Namespace) {
		for e in ns.members {
			mem := e.value
			match mem {
				Namespace: funNamespace(c, mem)
				FunctionDef: {
					fd := mem
					if (fd.flags & FunctionFlags.foreign) == 0 {
						if fd.tas != null {
							for ta in fd.tas {
								funVariant(c, fd, ta)
							}
						} else {
							funVariant(c, fd, null)
						}				
					}
				}
				default: {}
			}
		}
	}
	
	funVariant(c GenerateContext, fd FunctionDef, ta Array<Tag>) {
		sb := StringBuilder{}
		writeFunVariantSignature(ref sb, fd, ta)
		sb.write(" {")
		c.out.writeLine(sb.toString())
		c.out.indent()
		c.function = fd
		c.infoMap = fd.infoMap
		c.variantTagArgs = ta
		c.nextLocal = 0
		c.nextLabel = 0
		for st in fd.body.contents {
			genStatement(c, st)
		}
		c.out.unIndent()
		c.out.writeLine("}")
	}
	
	genStatement(c GenerateContext, st Node) {
		match st {
			BlockStatement: genBlockStatement(c, st)
			ExpressionStatement: genExpressionStatement(c, st)
			ReturnStatement: genReturnStatement(c, st)
			BreakStatement: genBreakStatement(c, st)
			ContinueStatement: genContinueStatement(c, st)
			IfStatement: genIfStatement(c, st)
			WhileStatement: genWhileStatement(c, st)
			ForEachStatement: genForEachStatement(c, st)
			ForIndexStatement: genForIndexStatement(c, st)
			MatchStatement: genMatchStatement(c, st)
		}
	}
	
	genBlockStatement(c GenerateContext, st BlockStatement) {
		c.out.writeLine("{")
		c.out.indent()
		for a in st.contents {
			genStatement(c, a)
		}
		c.out.unIndent()
		c.out.writeLine("}")
	}

	genExpressionStatement(c GenerateContext, st ExpressionStatement) {
		if st.expr.is(BinaryOperatorExpression) {
			bin := st.expr.as(BinaryOperatorExpression)
			if bin.op.value == ":=" {
				genDeclareStatement(c, bin)
				return
			} else if Parser.isAssignOp(bin.op.value) {
				genAssignStatement(c, bin)
				return
			}
		}
		c.currentExpressionStatement = st
		sb := StringBuilder{}
		genExpression(c, st.expr, ref sb)
		sb.write(";")
		c.out.writeLine(sb.toString())
		if c.emitUnreachable {
			genUnreachable(c)
			c.emitUnreachable = false
		}
	}
	
	genDeclareStatement(c GenerateContext, e BinaryOperatorExpression) {
		sb := new StringBuilder{}
		rhs := nodeTag(c, e.rhs)
		writeTag(sb, rhs)
		sb.write(" ")
		writeId(sb, e.lhs.as(Token))
		sb.write(" = ")
		genExpression(c, e.rhs, sb)
		sb.write(";")
		c.out.writeLine(sb.toString())
	}
	
	genAssignStatement(c GenerateContext, e BinaryOperatorExpression) {
		sb := new StringBuilder{}
		genExpression(c, e.lhs, sb)
		sb.write(" = ")
		if e.op.value == "=" {
			genConverted(c, e.rhs, sb, nodeTag(c, e.lhs))
		} else {
			op := e.op.value
			op = op.slice(0, op.length - 1)
			genBinaryOperatorExpression(c, op, e, sb)
		}
		sb.write(";")
		c.out.writeLine(sb.toString())
	}

	genReturnStatement(c GenerateContext, st ReturnStatement) {
		if st.expr == null {
			c.out.writeLine("return;")
			return
		}
		sb := StringBuilder{}
		sb.write("return ")
		closed := TypeChecker.closeTag(c.function.returnTag, c.function.typeParamList, c.variantTagArgs)
		genConverted(c, st.expr, ref sb, closed)
		sb.write(";")
		c.out.writeLine(sb.toString())
	}
	
	genBreakStatement(c GenerateContext, st BreakStatement) {
		sb := new StringBuilder{}
		sb.write("goto ")
		sb.write(c.breakLabel)
		sb.write(";")
		c.out.writeLine(sb.toString())
		c.breakCount += 1
	}

	genContinueStatement(c GenerateContext, st ContinueStatement) {
		sb := new StringBuilder{}
		sb.write("goto ")
		sb.write(c.continueLabel)
		sb.write(";")
		c.out.writeLine(sb.toString())
		c.continueCount += 1
	}

	genIfStatement(c GenerateContext, st IfStatement) {
		sb := new StringBuilder{}
		sb.write("if (")
		genExpression(c, st.conditionExpr, sb)
		sb.write(") {")
		c.out.writeLine(sb.toString())
		c.out.indent()
		for a in st.ifBranch.contents {
			genStatement(c, a)
		}
		c.out.unIndent()
		if st.elseBranch != null {
			c.out.writeLine("} else {")
			c.out.indent()
			elseBranch := st.elseBranch
			match elseBranch {
				IfStatement: {
					genIfStatement(c, elseBranch)
				}
				BlockStatement: {
					for a in elseBranch.contents {
						genStatement(c, a)
					}				
				}
			}
			c.out.unIndent()
		}
		c.out.writeLine("}")
	}

	genWhileStatement(c GenerateContext, st WhileStatement) {
		c.out.writeLine("while (true__) {")
		c.out.indent()
		
		sb := new StringBuilder{}
		sb.write("if (!(")
		genExpression(c, st.conditionExpr, sb)
		sb.write(")) { break; }")
		c.out.writeLine(sb.toString())
		sb.clear()

		prevBreakLabel := c.breakLabel
		prevBreakCount := c.breakCount
		prevContinueLabel := c.continueLabel
		prevContinueCount := c.continueCount
		c.breakLabel = newLabel(c)
		c.breakCount = 0
		c.continueLabel = newLabel(c)
		c.continueCount = 0

		for a in st.body.contents {
			genStatement(c, a)
		}

		if c.continueCount > 0 {
			c.out.unIndent()
			sb.write(c.continueLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			c.out.writeLine("{}")
		}
		
		c.out.unIndent()
		c.out.writeLine("}")
		
		if c.breakCount > 0 {
			c.out.unIndent()
			sb.write(c.breakLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			c.out.writeLine("{}")
		}

		c.breakLabel = prevBreakLabel
		c.breakCount = prevBreakCount
		c.continueLabel = prevContinueLabel
		c.continueCount = prevContinueCount
	}

	genForEachStatement(c GenerateContext, st ForEachStatement) {
		seqTag := nodeTag(c, st.sequenceExpr)
		addPtr := false
		if (seqTag.ti.flags & TypeFlags.ptr_) == 0 {
			seqTag = TypeChecker.getSingleArgTag(c.tags.ptrTi, seqTag) 			
			addPtr = true
		}

		seqTagNoPtr := seqTag.args[0]
		itTag := Tag{}
		entryTag := Tag{}
		count := ""
		dataPtr := ""
		unwrapValue := false
		checkHash := false
		if seqTagNoPtr.ti == c.tags.arrayTi || seqTagNoPtr.ti == c.tags.listTi {
			itTag = seqTagNoPtr.args[0]
			entryTag = seqTagNoPtr.args[0]
			count = "count__"
			dataPtr = "dataPtr__"
		} else if seqTagNoPtr.ti == c.tags.setTi || seqTagNoPtr.ti == c.tags.customSetTi {
			itTag = seqTagNoPtr.args[0]
			entryTag = TypeChecker.getSingleArgTag(c.tags.setEntryTi, itTag)
			count = "entries__.count__"
			dataPtr = "entries__.dataPtr__"
			unwrapValue = true
			checkHash = true
		} else if seqTagNoPtr.ti == c.tags.mapTi || seqTagNoPtr.ti == c.tags.customMapTi {
			itTag = Tag { ti: c.tags.mapEntryTi, args: seqTagNoPtr.args }
			entryTag = itTag
			count = "entries__.count__"
			dataPtr = "entries__.dataPtr__"
			checkHash = true
		} else {
			genFailure(c)
			return
		}

		entryTagPtr := TypeChecker.getSingleArgTag(c.tags.ptrTi, entryTag)
		
		c.out.writeLine("{")
		c.out.indent()
		sb := new StringBuilder{}
		seq := newLocal(c)
		
		writeTag(sb, seqTag)
		sb.write(" ")
		sb.write(seq)
		sb.write(" = ")
		if addPtr {
			genLvalueRefExpression(c, st.sequenceExpr, seqTagNoPtr, sb)
		} else {
			genExpression(c, st.sequenceExpr, sb)
		}
		sb.write(";")		
		c.out.writeLine(sb.toString())
		sb.clear()
		
		entryIndex := ""
		index := ""
		if seqTagNoPtr.ti == c.tags.setTi || seqTagNoPtr.ti == c.tags.mapTi {
			entryIndex = newLocal(c)
			index = st.indexIteratorVariable != null ? symId(st.indexIteratorVariable) : ""
		} else {
			entryIndex = st.indexIteratorVariable != null ? symId(st.indexIteratorVariable) : newLocal(c)
		}
		sb.write("int__ ")
		sb.write(entryIndex)
		sb.write(" = 0;")
		c.out.writeLine(sb.toString())
		sb.clear()

		if index != "" {
			sb.write("int__ ")
			sb.write(index)
			sb.write(" = 0;")
			c.out.writeLine(sb.toString())
			sb.clear()
		}

		sb.write("while (")
		sb.write(entryIndex)
		sb.write(" < ")
		sb.write(seq)
		sb.write("->")
		sb.write(count)
		sb.write(") {")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		c.out.indent()
		
		it := st.iteratorVariable != null ? symId(st.iteratorVariable) : "it__"
		entry := unwrapValue ? newLocal(c) : it
		writeTag(sb, entryTag)
		sb.write(" ")
		sb.write(entry)
		sb.write(" = ((")
		writeTag(sb, entryTagPtr)
		sb.write(")(")
		sb.write(seq)
		sb.write("->")
		sb.write(dataPtr)
		sb.write("))[")
		sb.write(entryIndex)
		sb.write("];")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		if checkHash {
			sb.write("if (")
			sb.write(entry)
			sb.write(".hash__ != 0) {")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			if unwrapValue {
				writeTag(sb, itTag)
				sb.write(" ")
				sb.write(it)
				sb.write(" = ")
				sb.write(entry)
				sb.write(".value__;")
				c.out.writeLine(sb.toString())
				sb.clear()
			}
		}

		prevBreakLabel := c.breakLabel
		prevBreakCount := c.breakCount
		prevContinueLabel := c.continueLabel
		prevContinueCount := c.continueCount
		c.breakLabel = newLabel(c)
		c.breakCount = 0
		c.continueLabel = newLabel(c)
		c.continueCount = 0
		
		for a in st.body.contents {
			genStatement(c, a)
		}
		
		if checkHash {
			if index != "" {
				sb.write(index)
				sb.write(" += 1;")
				c.out.writeLine(sb.toString())
				sb.clear()
			}
			c.out.unIndent()
			c.out.writeLine("}")
		}

		if c.continueCount > 0 {
			c.out.unIndent()
			sb.write(c.continueLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
		}
		
		sb.write(entryIndex)
		sb.write(" += 1;")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		c.out.unIndent()
		c.out.writeLine("}")
		
		if c.breakCount > 0 {
			c.out.unIndent()
			sb.write(c.breakLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			c.out.writeLine("{}")
		}

		c.out.unIndent()
		c.out.writeLine("}")

		c.breakLabel = prevBreakLabel
		c.breakCount = prevBreakCount
		c.continueLabel = prevContinueLabel
		c.continueCount = prevContinueCount
	}
		
	genForIndexStatement(c GenerateContext, st ForIndexStatement) {
		c.out.writeLine("{")
		c.out.indent()
		
		index := ""
		if st.initializeStatement != null {
			genExpressionStatement(c, st.initializeStatement)
			index = symId(st.initializeStatement.expr.as(BinaryOperatorExpression).lhs.as(Token))
		}
		
		sb := new StringBuilder{}
		sb.write("while (")
		genExpression(c, st.conditionExpr, sb)
		sb.write(") {")
		c.out.writeLine(sb.toString())
		sb.clear()
		c.out.indent()

		prevBreakLabel := c.breakLabel
		prevBreakCount := c.breakCount
		prevContinueLabel := c.continueLabel
		prevContinueCount := c.continueCount
		c.breakLabel = newLabel(c)
		c.breakCount = 0
		c.continueLabel = newLabel(c)
		c.continueCount = 0
		
		for a in st.body.contents {
			genStatement(c, a)
		}
		
		if c.continueCount > 0 {
			c.out.unIndent()
			sb.write(c.continueLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			c.out.writeLine("{}")
		}

		if st.nextStatement != null {
			genStatement(c, st.nextStatement)		
		} else if index != "" {
			sb.write(index)
			sb.write(" += 1;")
			c.out.writeLine(sb.toString())
		}
		
		c.out.unIndent()
		c.out.writeLine("}")
		
		if c.breakCount > 0 {
			c.out.unIndent()
			sb.write(c.breakLabel)
			sb.write(":")
			c.out.writeLine(sb.toString())
			sb.clear()
			c.out.indent()
			c.out.writeLine("{}")
		}

		c.out.unIndent()		
		c.out.writeLine("}")

		c.breakLabel = prevBreakLabel
		c.breakCount = prevBreakCount
		c.continueLabel = prevContinueLabel
		c.continueCount = prevContinueCount
	}

	genMatchStatement(c GenerateContext, st MatchStatement) {
		sb := new StringBuilder{}
		targetTag := nodeTag(c, st.expr)
		targetVar := st.expr.is(Token) ? st.expr.as(Token).value : ""
		
		sb.write("switch ((")
		genExpression(c, st.expr, sb)
		sb.write(").id__) {")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		c.out.indent()
		
		allCaseFlags := cast(0_u, MatchCaseFlags)
		for case in st.cases {
			if case.flags != 0 {
				if (case.flags & (MatchCaseFlags.null_ | MatchCaseFlags.default_)) == (MatchCaseFlags.null_ | MatchCaseFlags.default_) {
					c.out.writeLine("case 0:")
					c.out.writeLine("default: {")
				} else if (case.flags & MatchCaseFlags.null_) != 0 {
					c.out.writeLine("case 0: {")
				} else if (case.flags & MatchCaseFlags.default_) != 0 {
					c.out.writeLine("default: {")
				}
				c.out.indent()
				genStatement(c, case.statement)
				if !case.statement.is(BreakStatement) && !case.statement.is(ReturnStatement) {
					c.out.writeLine("break;")
				}

				c.out.unIndent()
				c.out.writeLine("}")
				allCaseFlags |= case.flags
			} else {
				sb.write("case ")
				writeNs(sb, targetTag.ti)
				sb.write("___")
				writeNestedTag(sb, case.tag)
				sb.write(": {")
				c.out.writeLine(sb.toString())
				sb.clear()

				c.out.indent()

				if targetVar != "" {
					alias := newLocal(c)
					c.aliasMap.add(targetVar, alias)

					writeTag(sb, case.tag)
					sb.write(" ")
					sb.write(alias)
					sb.write(" = (")
					writeTag(sb, case.tag)
					sb.write(")(")
					sb.write(targetVar)
					sb.write("__.dataPtr__);")
					c.out.writeLine(sb.toString())
					sb.clear()
				}
				
				genStatement(c, case.statement)
				if !case.statement.is(BreakStatement) && !case.statement.is(ReturnStatement) {
					c.out.writeLine("break;")
				}
				
				c.out.unIndent()
				c.out.writeLine("}")

				if targetVar != "" {
					c.aliasMap.remove(targetVar)
				}					
			}
		}
		
		unmatchedFlags := (~allCaseFlags) & (MatchCaseFlags.null_ | MatchCaseFlags.default_)
		if unmatchedFlags != 0 {
			if (unmatchedFlags & (MatchCaseFlags.null_ | MatchCaseFlags.default_)) == (MatchCaseFlags.null_ | MatchCaseFlags.default_) {
				c.out.writeLine("case 0:")
				c.out.writeLine("default: {")
			} else if (unmatchedFlags & MatchCaseFlags.null_) != 0 {
				c.out.writeLine("case 0: {")
			} else if (unmatchedFlags & MatchCaseFlags.default_) != 0 {
				c.out.writeLine("default: {")
			}
			c.out.indent()
			c.out.writeLine("mu_____nomatch();")
			genUnreachable(c)
			c.out.unIndent()
			c.out.writeLine("}")
		}

		c.out.unIndent()
		c.out.writeLine("}")
	}
	
	genExpression(c GenerateContext, e Node, sb StringBuilder) {
		match e {
			Token: genToken(c, e, sb)
			NumberExpression: genNumberExpression(c, e, sb)
			StringExpression: genStringExpression(c, e, sb)
			UnaryOperatorExpression: genUnaryOperatorExpression(c, e, sb)
			PostfixUnaryOperatorExpression: genPostfixUnaryOperatorExpression(c, e, sb)
			DotExpression: genDotExpression(c, e, sb)
			BinaryOperatorExpression: genBinaryOperatorExpression(c, e.op.value, e, sb)
			TernaryOperatorExpression: genTernaryOperatorExpression(c, e, sb)
			CallExpression: genCallExpression(c, e, sb)
			StructInitializerExpression: genStructInitializerExpression(c, e, sb)
			IndexExpression: genIndexExpression(c, e, sb)
			ParenExpression: genExpression(c, e.expr, sb)
		}
	}
	
	genToken(c GenerateContext, token Token, sb StringBuilder) {
		if token.type == TokenType.identifier {
			if token.value == "null" || token.value == "false" || token.value == "true" {
				writeId(sb, token)
				return
			}
			info := nodeInfo(c, token)
			mem := info.mem
			match mem {
				FunctionDef: writeFunRef(sb, mem)
				StaticFieldDef: writeStaticField(sb, mem)
				null: {
					alias := c.aliasMap.count > 0 ? c.aliasMap.maybeGet(token.value) : Maybe<string>{}
					if alias.hasValue {
						sb.write(alias.value)
					} else {
						writeId(sb, token)
					}
				}
			}
		} else if token.type == TokenType.characterLiteral {
			sb.write(token.value)
		} else {
			writeFailure(sb)
		}
	}
	
	genOpaqueValue(c GenerateContext, tag Tag, opaqueValue ulong, sb StringBuilder) {
		if (tag.ti.flags & TypeFlags.boolval) != 0 {
			sb.write(format("{}", opaqueValue))
		} else if (tag.ti.flags & TypeFlags.unsigned) != 0 {
			if opaqueValue == ulong.maxValue {
				// Note: this code path is here to cover up the fact that the c# interpreter doesn't like ulongs larger than long.maxValue
				sb.write("0xffffffffffffffffuLL")
			} else {
				sb.write(format("{}u", opaqueValue))
				if tag.ti.rank == 8 || (tag.ti.rank == 6 && (c.comp.flags & CompilationFlags.target32bit) == 0) {
					sb.write("LL")
				}
			}
		} else {
			value := transmute(opaqueValue, long)
			if tag.ti.rank == 8 || (tag.ti.rank == 6 && (c.comp.flags & CompilationFlags.target32bit) == 0) {
				if value > long.minValue {
					sb.write(format("{}LL", value))
				} else {
					sb.write(format("{}LL - 1", long.minValue + 1))
				}
			} else {
				if value > int.minValue {
					sb.write(format("{}", value))
				} else {
					sb.write(format("{} - 1", int.minValue + 1))
				}
			}
		}	
	}
	
	genNumberExpression(c GenerateContext, e NumberExpression, sb StringBuilder) {
		if (e.tag.ti.flags & TypeFlags.intval) != 0 {
			genOpaqueValue(c, e.tag, e.opaqueValue, sb)
		} else {
			localValueSpan := IntRange(e.valueSpan.from - e.token.span.from, e.valueSpan.to - e.token.span.from)
			sb.write(e.token.value.slice(localValueSpan.from, localValueSpan.to))
			if e.tag.ti == c.tags.float_.ti {
				sb.write("f")
			}
		}
	}

	genStringExpression(c GenerateContext, e StringExpression, sb StringBuilder) {
		sb.write(format("mu_____string{}", e.id))
	}

	genUnaryOperatorExpression(c GenerateContext, e UnaryOperatorExpression, sb StringBuilder) {
		op := e.op.value
		if op == "ref" {
			genLvalueRefExpression(c, e.expr, nodeTag(c, e.expr), sb)
		} else if op == "new" {
			genNewExpression(c, e, sb)
		} else if op == "~" || op == "!" || op == "-" {
			sb.write(op)
			sb.write("(")
			genExpression(c, e.expr, sb)
			sb.write(")")
		} else if op == "::" {
			info := nodeInfo(c, e)
			mem := info.mem
			match mem {
				FunctionDef: writeFunRef(sb, mem)
				StaticFieldDef: writeStaticField(sb, mem)
			}
		} else {
			writeFailure(sb)
		}
	}
	
	genLvalueRefExpression(c GenerateContext, e Node, tag Tag, rb StringBuilder) {
		if isLvalueApprox(c, e) {
			rb.write("&(")
			genExpression(c, e, rb)
			rb.write(")")
		} else {
			sb := new StringBuilder{}
			temp := newLocal(c)
			writeTag(sb, tag)
			sb.write(" ")
			sb.write(temp)
			sb.write(" = ")
			genExpression(c, e, sb)
			sb.write(";")
			c.out.writeLine(sb.toString())
			rb.write("&")
			rb.write(temp)
		}
	}
	
	// TODO: this is a hack, fix
	isLvalueApprox(c GenerateContext, e Node) {
		match e {
			CallExpression: {
				target := e.target
				if target.is(TypeArgsExpression) {
					target = target.as(TypeArgsExpression).target
				}
				targetInfo := nodeInfo(c, target)
				if targetInfo.mem.is(FunctionDef) {
					fd := targetInfo.mem.as(FunctionDef)
					if fd.builtin == BuiltinFunction.unchecked_index {
						return true
					}
				}
				return false
			}
			default: {
				return true
			}
		}		
	}
	
	genNewExpression(c GenerateContext, e UnaryOperatorExpression, rb StringBuilder) {
		result := newLocal(c)
		tag := nodeTag(c, e)
		sb := new StringBuilder{}
		writeTag(sb, tag)
		sb.write(" ")
		sb.write(result)
		sb.write(" = (")
		writeTag(sb, tag)
		sb.write(")top_____currentAllocator__.allocFn__(top_____currentAllocator__.data__, sizeof(")
		writeTag(sb, nodeTag(c, e.expr))
		sb.write("));")
		c.out.writeLine(sb.toString())
		sb.clear()
		sb.write("*")
		sb.write(result)
		sb.write(" = ")
		genExpression(c, e.expr, sb)
		sb.write(";")
		c.out.writeLine(sb.toString())
		rb.write(result)
	}

	genPostfixUnaryOperatorExpression(c GenerateContext, e PostfixUnaryOperatorExpression, sb StringBuilder) {
		assert(e.op.value == "^")
		sb.write("*(")
		genExpression(c, e.expr, sb)
		sb.write(")")		
	}

	genDotExpression(c GenerateContext, e DotExpression, sb StringBuilder) {
		info := nodeInfo(c, e)
		li := nodeInfo(c, e.lhs)
		if li.mem.is(Namespace) {
			mem := info.mem
			match mem {
				FunctionDef: writeFunRef(sb, mem)
				StaticFieldDef: writeStaticField(sb, mem)
			}
		} else {
			sb.write("(")
			genExpression(c, e.lhs, sb)
			sb.write(")")
			mem := info.mem.as(FieldDef)
			isPtr := (li.tag.ti.flags & TypeFlags.ptr_) != 0
			sb.write(isPtr ? "->" : ".")
			writeId(sb, e.rhs)
		}
	}

	genBinaryOperatorExpression(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder) {
		if op == "+" || op == "-" {
			tag := nodeTag(c, e)
			if tag.ti == c.tags.pointer_.ti {
				genBasicBinaryOperator(c, op, e, sb)
			} else if tag.ti == c.tags.char_.ti {
				sb.write("(char__)(")
				genBasicBinaryOperator(c, op, e, sb)
				sb.write(")")
			} else {
				genLiftingBinaryOperator(c, op, e.lhs, e.rhs, sb, tag)
			}			
		} else if op == "*" || op == "/" || op == "%" || op == "&" || op == "|" {
			tag := nodeTag(c, e)
			genLiftingBinaryOperator(c, op, e.lhs, e.rhs, sb, tag)
		} else if op == "&&" || op == "||" {
			genShortCircuitOperator(c, op, e, sb)
		} else if op == ">>" || op == "<<" {
			genBitshiftOperator(c, op, e, sb)
		} else if op == "==" || op == "!=" {
			genCompareEqualsOperator(c, op, e, sb)
		} else if op == ">" || op == "<" || op == "<=" || op == ">=" {
			genCompareOrderedOperator(c, op, e, sb)
		}
	}

	genBasicBinaryOperator(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder) {
		sb.write("(")
		genExpression(c, e.lhs, sb)
		sb.write(") ")
		sb.write(op)
		sb.write(" (")
		genExpression(c, e.rhs, sb)
		sb.write(")")
	}

	genLiftingBinaryOperator(c GenerateContext, op string, lhs Node, rhs Node, sb StringBuilder, tag Tag) {
		sb.write("(")
		if tag.ti != nodeTag(c, lhs).ti {
			sb.write("(")
			writeTag(sb, tag)
			sb.write(")")
		}
		sb.write("(")
		genExpression(c, lhs, sb)
		sb.write(")) ")
		sb.write(op)
		sb.write(" (")
		if tag.ti != nodeTag(c, rhs).ti {
			sb.write("(")
			writeTag(sb, tag)
			sb.write(")")
		}
		sb.write("(")
		genExpression(c, rhs, sb)
		sb.write("))")
	}
	
	genShortCircuitOperator(c GenerateContext, op string, e BinaryOperatorExpression, rb StringBuilder) {
		result := newLocal(c)
		tag := nodeTag(c, e)
		sb := new StringBuilder{}
		writeTag(sb, tag)
		sb.write(" ")
		sb.write(result)
		sb.write(" = ")
		genExpression(c, e.lhs, sb)
		sb.write(";")		
		c.out.writeLine(sb.toString())
		sb.clear()
		sb.write("if (")
		if op == "||" {
			sb.write("!")
		}
		sb.write(result)
		sb.write(") {")
		c.out.writeLine(sb.toString())
		sb.clear()
		c.out.indent()		
		sb.write(result)
		sb.write(" = ")
		genExpression(c, e.rhs, sb)
		sb.write(";")				
		c.out.writeLine(sb.toString())
		c.out.unIndent()
		c.out.writeLine("}")
		rb.write(result)
	}
	
	genBitshiftOperator(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder) {
		tag := nodeTag(c, e)
		sb.write("((")
		writeTag(sb, tag)
		sb.write(")")
		genExpression(c, e.lhs, sb)
		sb.write(") ")
		sb.write(op)
		sb.write(" (")
		genExpression(c, e.rhs, sb)
		sb.write(")")			
	}
	
	genCompareEqualsOperator(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder) {
		lhs := nodeTag(c, e.lhs)
		rhs := nodeTag(c, e.rhs)
		if (lhs.ti.flags & TypeFlags.intval) != 0 {
			uni := TypeChecker.tryUnifyIntvals(c.tags, lhs, e.lhs, rhs, e.rhs)
			genLiftingBinaryOperator(c, op, e.lhs, e.rhs, sb, uni)
		} else if (lhs.ti.flags & TypeFlags.struct_) != 0 {
			// TODO: support generics
			if op == "!=" {
				sb.write("!")
			} else {
				assert(op == "==")
			}
			writeNs(sb, lhs.ti)
			sb.write("equals__(")
			genExpression(c, e.lhs, sb)
			sb.write(", ")
			genExpression(c, e.rhs, sb)
			sb.write(")")		
		} else if (lhs.ti.flags & TypeFlags.taggedPointerEnum) != 0 || (rhs.ti.flags & TypeFlags.taggedPointerEnum) != 0 {
			genTaggedPointerEqualsOperator(c, op, e, sb, lhs, rhs)
		} else {
			genBasicBinaryOperator(c, op, e, sb)
		}
	}
	
	genTaggedPointerEqualsOperator(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder, lhs Tag, rhs Tag) {
		sb.write("(")
		if (lhs.ti.flags & TypeFlags.taggedPointerEnum) != 0 {
			sb.write("(")
			genExpression(c, e.lhs, sb)
			sb.write(").dataPtr__")
		} else {
			sb.write("(pointer__)(")
			genExpression(c, e.lhs, sb)
			sb.write(")")
		}
		sb.write(") ")
		sb.write(op)
		sb.write(" (")
		if (rhs.ti.flags & TypeFlags.taggedPointerEnum) != 0 {
			sb.write("(")
			genExpression(c, e.rhs, sb)
			sb.write(").dataPtr__")
		} else {
			sb.write("(pointer__)(")
			genExpression(c, e.rhs, sb)
			sb.write(")")
		}
		sb.write(")")
	}
	
	genCompareOrderedOperator(c GenerateContext, op string, e BinaryOperatorExpression, sb StringBuilder) {
		lhs := nodeTag(c, e.lhs)
		if (lhs.ti.flags & TypeFlags.anyNumber) != 0 {
			rhs := nodeTag(c, e.rhs)
			uni := TypeChecker.tryUnifyNumbers(c.tags, lhs, e.lhs, rhs, e.rhs)
			genLiftingBinaryOperator(c, op, e.lhs, e.rhs, sb, uni)
		} else if (lhs.ti.flags & TypeFlags.pointer_) != 0 {
			genBasicBinaryOperator(c, op, e, sb)
		} else if lhs.ti == c.tags.char_.ti {
			sb.write("((unsigned char)(")
			genExpression(c, e.lhs, sb)
			sb.write(")) ")
			sb.write(op)
			sb.write(" ((unsigned char)(")
			genExpression(c, e.rhs, sb)
			sb.write("))")
		} else if lhs.ti == c.tags.string_.ti {
			sb.write("string__compare__(")
			genExpression(c, e.lhs, sb)
			sb.write(", ")
			genExpression(c, e.rhs, sb)
			sb.write(") ")
			sb.write(op)
			sb.write(" 0")
		} else {
			genBasicBinaryOperator(c, op, e, sb)
		}
	}

	genTernaryOperatorExpression(c GenerateContext, e TernaryOperatorExpression, rb StringBuilder) {
		tag := nodeTag(c, e)
		temp := newLocal(c)
		sb := new StringBuilder{}
		writeTag(sb, tag)
		sb.write(" ")
		sb.write(temp)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		sb.write("if (")
		genExpression(c, e.conditionExpr, sb)
		sb.write(") {")
		c.out.writeLine(sb.toString())
		sb.clear()
		c.out.indent()

		sb.write(temp)
		sb.write(" = ")
		genConverted(c, e.trueExpr, sb, tag)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()

		c.out.unIndent()
		c.out.writeLine("} else {")
		c.out.indent()

		sb.write(temp)
		sb.write(" = ")
		genConverted(c, e.falseExpr, sb, tag)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		c.out.unIndent()
		c.out.writeLine("}")
		
		rb.write(temp)
	}

	genCallExpression(c GenerateContext, e CallExpression, sb StringBuilder) {
		target := e.target
		if target.is(TypeArgsExpression) {
			target = target.as(TypeArgsExpression).target
		}
		targetInfo := nodeInfo(c, target)
		fd := cast(null, FunctionDef)
		exprTa := e.ta != null ? TypeChecker.closeTagArgs(e.ta, c.function.typeParamList, c.variantTagArgs) : null
		implicitArg := cast(null, Node)
		if targetInfo.mem.is(FunctionDef) {
			fd = targetInfo.mem.as(FunctionDef)
			if target.is(DotExpression) {
				dot := target.as(DotExpression)
				lhs := nodeTag(c, dot.lhs)
				if lhs.ti != null {
					implicitArg = dot.lhs
				}
			}
			if fd.builtin != BuiltinFunction.none {
				genBuiltin(c, fd, e, implicitArg, sb)
				return
			} else if (fd.flags & FunctionFlags.foreign) != 0 {
				genForeignCall(c, fd, e, implicitArg, sb)
				return
			} else {
				writeFunVariant(sb, fd, exprTa)
			}
		} else {
			genExpression(c, e.target, sb)
		}
		sb.write("(")
		insertSep := false
		bias := 0
		if implicitArg != null {
			to := TypeChecker.closeTag(fd.params[0].tag, fd.typeParamList, exprTa)
			implicitArgTag := nodeTag(c, implicitArg)
			if to.ti == c.tags.ptrTi && implicitArgTag.ti != c.tags.ptrTi {
				genLvalueRefExpression(c, implicitArg, implicitArgTag, sb)
			} else {
				genConverted(c, implicitArg, sb, to)
			}			
			insertSep = true
			bias = 1
		}
		for a, i in e.args {
			if insertSep {
				sb.write(", ")
			} else {
				insertSep = true
			}
			if fd != null {
				p := fd.params[i + bias]	
				to := TypeChecker.closeTag(p.tag, fd.typeParamList, exprTa)
				genConverted(c, a, sb, to)
			} else {
				genConverted(c, a, sb, targetInfo.tag.args[i])
			}
		}
		sb.write(")")
	}
	
	genForeignCall(c GenerateContext, fd FunctionDef, e CallExpression, implicitArg Node, sb StringBuilder) {
		if (fd.flags & FunctionFlags.marshalReturnType) != 0 && (fd.returnTag.ti.flags & TypeFlags.struct_) != 0 {
			genForeignMarshalStructReturnTypeCall(c, fd, e, implicitArg, sb)
		} else {
			if !(fd.returnTag.ti == c.tags.void_.ti || (c.currentExpressionStatement != null && c.currentExpressionStatement.expr == e)) {
				sb.write("(")
				writeTag(sb, fd.returnTag)
				sb.write(")")
			}
			genForeignCallImpl(c, fd, e, implicitArg, sb)
		}
	}
		
	genForeignMarshalStructReturnTypeCall(c GenerateContext, fd FunctionDef, e CallExpression, implicitArg Node, rb StringBuilder) {
		sb := new StringBuilder{}
		temp := newLocal(c)
		sb.write("union { ")
		sb.write(fd.marshalReturnType)
		sb.write(" from; ")
		writeTag(sb, fd.returnTag)
		sb.write(" to; } ")
		sb.write(temp)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		sb.write(temp)
		sb.write(".from = ")
		genForeignCallImpl(c, fd, e, implicitArg, sb)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		rb.write(temp)
		rb.write(".to")
	}
	
	genForeignCallImpl(c GenerateContext, fd FunctionDef, e CallExpression, implicitArg Node, sb StringBuilder) {
		sb.write(fd.foreignName)
		sb.write("(")
		insertSep := false
		bias := 0
		if implicitArg != null {
			genForeignArg(c, implicitArg, fd.params[0], true, sb)
			insertSep = true
			bias = 1
		}
		for a, i in e.args {
			if insertSep {
				sb.write(", ")
			} else {
				insertSep = true
			}
			index := i + bias
			genForeignArg(c, a, index < fd.params.count ? fd.params[index] : null, false, sb)
		}
		sb.write(")")
	}
	
	genForeignArg(c GenerateContext, arg Node, p Param, isImplicitArg bool, sb StringBuilder) {
		if p == null {
			genExpression(c, arg, sb)		
		} else if (p.flags & ParamFlags.marshalType) != 0 {
			if (p.tag.ti.flags & TypeFlags.struct_) != 0 {
				genForeignMarshalStructArg(c, arg, p, isImplicitArg, sb)
			} else {
				sb.write("(")
				sb.write(p.marshalType)
				sb.write(")")
				genForeignArgImpl(c, arg, p, isImplicitArg, sb)
			}
		} else {
			genForeignArgImpl(c, arg, p, isImplicitArg, sb)
		}
	}
	
	genForeignMarshalStructArg(c GenerateContext, arg Node, p Param, isImplicitArg bool, rb StringBuilder) {
		sb := new StringBuilder{}
		temp := newLocal(c)
		sb.write("union { ")
		writeTag(sb, p.tag)
		sb.write(" from; ")
		sb.write(p.marshalType)
		sb.write(" to; } ")
		sb.write(temp)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		sb.write(temp)
		sb.write(".from = ")
		genForeignArgImpl(c, arg, p, isImplicitArg, sb)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		rb.write(temp)
		rb.write(".to")
	}
	
	genForeignArgImpl(c GenerateContext, arg Node, p Param, isImplicitArg bool, sb StringBuilder) {
		if isImplicitArg {
			to := p.tag
			implicitArgTag := nodeTag(c, arg)
			if to.ti == c.tags.ptrTi && implicitArgTag.ti != c.tags.ptrTi {
				genLvalueRefExpression(c, arg, implicitArgTag, sb)
			} else {
				genConverted(c, arg, sb, to)
			}			
		} else {
			genConverted(c, arg, sb, p.tag)
		}
	}

	genStructInitializerExpression(c GenerateContext, e StructInitializerExpression, rb StringBuilder) {
		result := newLocal(c)
		tag := nodeTag(c, e)
		sb := new StringBuilder{}
		writeTag(sb, tag)
		sb.write(" ")
		sb.write(result)
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		sb.write("memset(&")
		sb.write(result)
		sb.write(", 0, sizeof(")
		writeTag(sb, tag)
		sb.write("));")
		c.out.writeLine(sb.toString())
		for fie in e.args {
			f := tag.ti.members.get(fie.fieldName.value).as(FieldDef)
			closed := TypeChecker.closeTag(f.tag, tag.ti.typeParamList, tag.args)
			sb.clear()	
			sb.write(result)
			sb.write(".")
			writeId(sb, fie.fieldName)
			sb.write(" = ")
			genConverted(c, fie.expr, sb, closed)
			sb.write(";")
			c.out.writeLine(sb.toString())
		}
		rb.write(result)
	}

	genIndexExpression(c GenerateContext, e IndexExpression, rb StringBuilder) {
		genIndex(c, e.target, e.arg, nodeTag(c, e), true, rb)
	}
	
	genIndex(c GenerateContext, target Node, arg Node, elementTag Tag, boundsCheck bool, rb StringBuilder) {
		seq := newLocal(c)
		seqTag := nodeTag(c, target)
		addPtr := false
		if (seqTag.ti.flags & TypeFlags.ptr_) == 0 {
			seqTag = TypeChecker.getSingleArgTag(c.tags.ptrTi, seqTag) 
			addPtr = true
		}

		sb := new StringBuilder{}
		writeTag(sb, seqTag)
		sb.write(" ")
		sb.write(seq)
		sb.write(" = ")
		if addPtr {
			genLvalueRefExpression(c, target, seqTag.args[0], sb)
		} else {
			genExpression(c, target, sb)
		}
		sb.write(";")
		c.out.writeLine(sb.toString())
		sb.clear()
		
		index := ""
		if boundsCheck {
			index = newLocal(c)
			sb.write("uint__ ")
			sb.write(index)
			sb.write(" = (uint__)(")
			genExpression(c, arg, sb)
			sb.write(");")
			c.out.writeLine(sb.toString())
			sb.clear()
			
			if seqTag.args[0].ti == c.tags.string_.ti {
				sb.write("mu_____boundscheck(")
				sb.write(index)
				sb.write(" < (uint__)")
				sb.write(seq)
				sb.write("->length__);")
				c.out.writeLine(sb.toString())
			} else {
				sb.write("mu_____boundscheck(")
				sb.write(index)
				sb.write(" < (uint__)")
				sb.write(seq)
				sb.write("->count__);")
				c.out.writeLine(sb.toString())
			}
		}
		
		rb.write("((")
		elementTagPtr := TypeChecker.getSingleArgTag(c.tags.ptrTi, elementTag) 
		writeTag(rb, elementTagPtr)
		rb.write(")")
		rb.write(seq)
		rb.write("->dataPtr__)[")
		if boundsCheck {
			rb.write(index)
		} else {
			genExpression(c, arg, rb)
		}
		rb.write("]")
	}
	
	newLocal(c GenerateContext) {
		result := format("local{}", c.nextLocal)
		c.nextLocal += 1
		return result
	}

	newLabel(c GenerateContext) {
		result := format("label{}", c.nextLabel)
		c.nextLabel += 1
		return result
	}

	nodeTag(c GenerateContext, node Node) {
		info := c.infoMap.get(node)
		return TypeChecker.closeTag(info.tag, c.function.typeParamList, c.variantTagArgs)
	}
	
	nodeInfo(c GenerateContext, node Node) {
		info := c.infoMap.get(node)
		return NodeInfo { tag: TypeChecker.closeTag(info.tag, c.function.typeParamList, c.variantTagArgs), mem: info.mem }
	}

	writeFun(sb StringBuilder, fd FunctionDef) {
		writeNs(sb, fd.ns)
		writeId(sb, fd.name)
	}
	
	writeFunVariant(sb StringBuilder, fd FunctionDef, ta Array<Tag>) {
		writeNs(sb, fd.ns)
		writeId(sb, fd.name)
		if ta != null {
			writeTagArgs(sb, ta)
		}
	}
	
	writeFunRef(sb StringBuilder, fd FunctionDef) {
		sb.write("&")
		writeNs(sb, fd.ns)
		writeId(sb, fd.name)		
	}
	
	writeStaticField(sb StringBuilder, sf StaticFieldDef) {
		writeNs(sb, sf.ns)
		writeId(sb, sf.name)		
	}
	
	writeNs(sb StringBuilder, ns Namespace) {
		if ns.parent == null {
			sb.write(ns.name)
			sb.write("__")
		} else {
			if ns.parent.parent != null {
				writeNs(sb, ns.parent)
			}			
			sb.write(ns.name)
			sb.write("__")
		}
	}
	
	symNs(ns Namespace) {
		if ns.parent != null && ns.parent.parent == null {
			return format("{}__", ns.name)
		}
		rb := StringBuilder{}
		writeNs(ref rb, ns)
		return rb.toString()
	}
	
	writeTag(sb StringBuilder, tag Tag) {
		ptrCount := 0
		while (tag.ti.flags & TypeFlags.ptr_) != 0 {
			ptrCount += 1
			tag = tag.args[0]
		}
		writeNs(sb, tag.ti)
		if tag.args != null {
			for a in tag.args {
				writeNestedTag(sb, a)
			}
			sb.write("__")
		}
		for i := 0; i < ptrCount {
			sb.write("*")
		}
	}
	
	writeNestedTag(sb StringBuilder, tag Tag) {
		writeNs(sb, tag.ti)
		if tag.args != null {
			for a in tag.args {
				writeNestedTag(sb, a)
			}
			sb.write("__")
		}
	}
	
	writeTagArgs(sb StringBuilder, ta Array<Tag>) {
		for a in ta {
			writeNestedTag(sb, a)
		}
		sb.write("__")
	}

	symTag(tag Tag) {
		if tag.args == null {
			return symNs(tag.ti)
		}
		rb := StringBuilder{}
		writeTag(ref rb, tag)
		return rb.toString()
	}
	
	symNestedTag(tag Tag) {
		if tag.args == null {
			return symNs(tag.ti)
		}
		rb := StringBuilder{}
		writeNestedTag(ref rb, tag)
		return rb.toString()
	}

	writeId(sb StringBuilder, token Token) {
		sb.write(token.value)
		sb.write("__")
	}
	
	symId(token Token) {
		return format("{}__", token.value)
	}
	
	genFailure(c GenerateContext) {
		c.out.writeLine("___GENERATION_FAILED___")
	}	

	writeFailure(sb StringBuilder) {
		sb.write("___GENERATION_FAILED___")
	}	
	
	:hexDigits = "0123456789abcdef"
	
	writeUnescapedString(sb StringBuilder, s string) {
		for i := 0; i < s.length {
			ch := s[i]
			code := transmute(ch, int)
			if ch == '"' {
				sb.write("\\\"")
			} else if ch == '\\' {
				sb.write("\\\\")
			} else if 32 <= code && code < 127 {
				sb.writeChar(ch)
			} else {
				sb.write("\\x")
				writeByteHexValue(sb, code)
				sb.write("\"\"")
			}
		}
		return sb.toString()
	}
	
	writeByteHexValue(sb StringBuilder, value int) {
		sb.writeChar(hexDigits[value / 16])
		sb.writeChar(hexDigits[value & 0xf])
	}
}

