...
This commit is contained in:
@@ -8,6 +8,8 @@ namespace NubLang.Generation.QBE;
|
||||
|
||||
public class QBEGenerator
|
||||
{
|
||||
private const int PTR_SIZE = 8;
|
||||
|
||||
private readonly QBEWriter _writer;
|
||||
private readonly List<DefinitionNode> _definitions;
|
||||
private readonly HashSet<StructTypeNode> _structTypes;
|
||||
@@ -20,7 +22,6 @@ public class QBEGenerator
|
||||
private int _labelIndex;
|
||||
private int _cStringLiteralIndex;
|
||||
private int _stringLiteralIndex;
|
||||
private bool _codeIsReachable = true;
|
||||
|
||||
public QBEGenerator(List<DefinitionNode> definitions, HashSet<StructTypeNode> structTypes)
|
||||
{
|
||||
@@ -39,7 +40,6 @@ public class QBEGenerator
|
||||
_labelIndex = 0;
|
||||
_cStringLiteralIndex = 0;
|
||||
_stringLiteralIndex = 0;
|
||||
_codeIsReachable = true;
|
||||
|
||||
_writer.Comment("========== Builtin functions ==========");
|
||||
|
||||
@@ -142,77 +142,125 @@ public class QBEGenerator
|
||||
|
||||
private static string QBEAssign(TypeNode type)
|
||||
{
|
||||
if (type.IsSimpleType(out var simpleType, out _))
|
||||
return type switch
|
||||
{
|
||||
return simpleType.StorageSize switch
|
||||
{
|
||||
StorageSize.I8 or StorageSize.U8 or StorageSize.I16 or StorageSize.U16 or StorageSize.I32 or StorageSize.U32 => "=w",
|
||||
StorageSize.I64 or StorageSize.U64 => "=l",
|
||||
StorageSize.F32 => "=s",
|
||||
StorageSize.F64 => "=d",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
|
||||
};
|
||||
}
|
||||
|
||||
return "=l";
|
||||
IntTypeNode { Width: <= 32 } => "=w",
|
||||
IntTypeNode { Width: 64 } => "=l",
|
||||
FloatTypeNode { Width: 32 } => "=s",
|
||||
FloatTypeNode { Width: 64 } => "=d",
|
||||
BoolTypeNode => "=w",
|
||||
PointerTypeNode => "=l",
|
||||
FuncTypeNode => "=l",
|
||||
CStringTypeNode => "=l",
|
||||
StringTypeNode => "=l",
|
||||
ArrayTypeNode => "=l",
|
||||
StructTypeNode => throw new InvalidOperationException("Structs are not loaded/stored directly"),
|
||||
VoidTypeNode => throw new InvalidOperationException("Void has no assignment"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
private void EmitStore(TypeNode type, string value, string destination)
|
||||
{
|
||||
string store;
|
||||
|
||||
if (type.IsSimpleType(out var simpleType, out _))
|
||||
var store = type switch
|
||||
{
|
||||
store = simpleType.StorageSize switch
|
||||
{
|
||||
StorageSize.I8 or StorageSize.U8 => "storeb",
|
||||
StorageSize.I16 or StorageSize.U16 => "storeh",
|
||||
StorageSize.I32 or StorageSize.U32 => "storew",
|
||||
StorageSize.I64 or StorageSize.U64 => "storel",
|
||||
StorageSize.F32 => "stores",
|
||||
StorageSize.F64 => "stored",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
store = "storel";
|
||||
}
|
||||
BoolTypeNode => "storeb",
|
||||
IntTypeNode { Width: 8 } => "storeb",
|
||||
IntTypeNode { Width: 16 } => "storeh",
|
||||
IntTypeNode { Width: 32 } => "storew",
|
||||
IntTypeNode { Width: 64 } => "storel",
|
||||
FloatTypeNode { Width: 32 } => "stores",
|
||||
FloatTypeNode { Width: 64 } => "stored",
|
||||
PointerTypeNode => "storel",
|
||||
FuncTypeNode => "storel",
|
||||
CStringTypeNode => "storel",
|
||||
StringTypeNode => "storel",
|
||||
ArrayTypeNode => "storel",
|
||||
StructTypeNode => throw new InvalidOperationException("Struct stores must use blit/memcpy"),
|
||||
VoidTypeNode => throw new InvalidOperationException("Cannot store void"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}")
|
||||
};
|
||||
|
||||
_writer.Indented($"{store} {value}, {destination}");
|
||||
}
|
||||
|
||||
private string EmitLoad(TypeNode type, string from)
|
||||
{
|
||||
string load;
|
||||
|
||||
if (type.IsSimpleType(out var simpleType, out _))
|
||||
string load = type switch
|
||||
{
|
||||
load = simpleType.StorageSize switch
|
||||
{
|
||||
StorageSize.I64 or StorageSize.U64 => "loadl",
|
||||
StorageSize.I32 or StorageSize.U32 => "loadw",
|
||||
StorageSize.I16 => "loadsh",
|
||||
StorageSize.I8 => "loadsb",
|
||||
StorageSize.U16 => "loaduh",
|
||||
StorageSize.U8 => "loadub",
|
||||
StorageSize.F64 => "loadd",
|
||||
StorageSize.F32 => "loads",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
load = "loadl";
|
||||
}
|
||||
BoolTypeNode => "loadub",
|
||||
IntTypeNode { Signed: true, Width: 8 } => "loadsb",
|
||||
IntTypeNode { Signed: true, Width: 16 } => "loadsh",
|
||||
IntTypeNode { Signed: true, Width: 32 } => "loadw",
|
||||
IntTypeNode { Signed: true, Width: 64 } => "loadl",
|
||||
IntTypeNode { Signed: false, Width: 8 } => "loadsb",
|
||||
IntTypeNode { Signed: false, Width: 16 } => "loadsh",
|
||||
IntTypeNode { Signed: false, Width: 32 } => "loadw",
|
||||
IntTypeNode { Signed: false, Width: 64 } => "loadl",
|
||||
FloatTypeNode { Width: 32 } => "loads",
|
||||
FloatTypeNode { Width: 64 } => "loadd",
|
||||
PointerTypeNode => "loadl",
|
||||
FuncTypeNode => "loadl",
|
||||
CStringTypeNode => "loadl",
|
||||
StringTypeNode => "loadl",
|
||||
ArrayTypeNode => "loadl",
|
||||
StructTypeNode => throw new InvalidOperationException("Struct loads must use blit/memcpy"),
|
||||
VoidTypeNode => throw new InvalidOperationException("Cannot load void"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}")
|
||||
};
|
||||
|
||||
var into = TmpName();
|
||||
|
||||
_writer.Indented($"{into} {QBEAssign(type)} {load} {from}");
|
||||
|
||||
return into;
|
||||
}
|
||||
|
||||
|
||||
private static string StructDefQBEType(TypeNode type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
BoolTypeNode => "b",
|
||||
IntTypeNode { Width: 8 } => "b",
|
||||
IntTypeNode { Width: 16 } => "h",
|
||||
IntTypeNode { Width: 32 } => "w",
|
||||
IntTypeNode { Width: 64 } => "l",
|
||||
FloatTypeNode { Width: 32 } => "s",
|
||||
FloatTypeNode { Width: 64 } => "d",
|
||||
PointerTypeNode => "l",
|
||||
FuncTypeNode => "l",
|
||||
CStringTypeNode => "l",
|
||||
StringTypeNode => "l",
|
||||
ArrayTypeNode => "l",
|
||||
StructTypeNode st => StructTypeName(st.Module, st.Name),
|
||||
VoidTypeNode => throw new InvalidOperationException("Void has no QBE type"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
private string FuncQBETypeName(TypeNode type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
BoolTypeNode => "ub",
|
||||
IntTypeNode { Signed: true, Width: 8 } => "sb",
|
||||
IntTypeNode { Signed: true, Width: 16 } => "sh",
|
||||
IntTypeNode { Signed: false, Width: 8 } => "ub",
|
||||
IntTypeNode { Signed: false, Width: 16 } => "uh",
|
||||
IntTypeNode { Width: 32 } => "w",
|
||||
IntTypeNode { Width: 64 } => "l",
|
||||
FloatTypeNode { Width: 32 } => "s",
|
||||
FloatTypeNode { Width: 64 } => "d",
|
||||
PointerTypeNode => "l",
|
||||
FuncTypeNode => "l",
|
||||
CStringTypeNode => "l",
|
||||
StringTypeNode => "l",
|
||||
ArrayTypeNode => "l",
|
||||
StructTypeNode st => StructTypeName(st.Module, st.Name),
|
||||
VoidTypeNode => throw new InvalidOperationException("Void has no QBE type"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
private void EmitMemset(string destination, int value, string length)
|
||||
{
|
||||
_writer.Indented($"call $.memset(l {destination}, l {value}, l {length})");
|
||||
@@ -250,109 +298,100 @@ public class QBEGenerator
|
||||
|
||||
private void EmitCopyInto(ExpressionNode source, string destination)
|
||||
{
|
||||
// Simple types are passed in registers and can therefore just be stored
|
||||
if (source.Type.IsSimpleType(out var simpleType, out var complexType))
|
||||
if (source.Type.IsScalar)
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
EmitStore(simpleType, value, destination);
|
||||
EmitStore(source.Type, EmitExpression(source), destination);
|
||||
return;
|
||||
}
|
||||
|
||||
// Structs has known sizes at compile time
|
||||
if (complexType is StructTypeNode)
|
||||
if (source.Type.IsValueType)
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
_writer.Indented($"blit {value}, {destination}, {SizeOf(complexType)}");
|
||||
_writer.Indented($"blit {value}, {destination}, {SizeOf(source.Type)}");
|
||||
return;
|
||||
}
|
||||
// The rest of the complex types has unknown sizes
|
||||
else
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
var size = complexType switch
|
||||
{
|
||||
ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
|
||||
CStringTypeNode => EmitCStringSizeInBytes(value),
|
||||
StringTypeNode => EmitStringSizeInBytes(value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(source.Type))
|
||||
};
|
||||
|
||||
var buffer = TmpName();
|
||||
_writer.Indented($"{buffer} =l alloc8 {size}");
|
||||
EmitMemcpy(value, buffer, size);
|
||||
EmitStore(complexType, buffer, destination);
|
||||
switch (source.Type)
|
||||
{
|
||||
case ArrayTypeNode arrayType:
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
var size = EmitArraySizeInBytes(arrayType, value);
|
||||
var buffer = TmpName();
|
||||
_writer.Indented($"{buffer} =l alloc8 {size}");
|
||||
EmitMemcpy(value, buffer, size);
|
||||
EmitStore(arrayType, buffer, destination);
|
||||
return;
|
||||
}
|
||||
case CStringTypeNode:
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
var size = EmitCStringSizeInBytes(value);
|
||||
var buffer = TmpName();
|
||||
_writer.Indented($"{buffer} =l alloc8 {size}");
|
||||
EmitMemcpy(value, buffer, size);
|
||||
EmitStore(source.Type, buffer, destination);
|
||||
return;
|
||||
}
|
||||
case StringTypeNode:
|
||||
{
|
||||
var value = EmitExpression(source);
|
||||
var size = EmitStringSizeInBytes(value);
|
||||
var buffer = TmpName();
|
||||
_writer.Indented($"{buffer} =l alloc8 {size}");
|
||||
EmitMemcpy(value, buffer, size);
|
||||
EmitStore(source.Type, buffer, destination);
|
||||
return;
|
||||
}
|
||||
case VoidTypeNode:
|
||||
throw new InvalidOperationException("Cannot copy void");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(source.Type), $"Unknown type {source.Type}");
|
||||
}
|
||||
}
|
||||
|
||||
private string EmitCopy(ExpressionNode source)
|
||||
{
|
||||
// Allowlist for types which are safe to not copy
|
||||
if (source is ArrayInitializerNode or StructInitializerNode or LiteralNode)
|
||||
if (source is RValueExpressionNode || source.Type.IsScalar)
|
||||
{
|
||||
return EmitExpression(source);
|
||||
}
|
||||
|
||||
// Simple types are passed in registers and therefore always copied
|
||||
if (source.Type.IsSimpleType(out _, out var complexType))
|
||||
{
|
||||
return EmitExpression(source);
|
||||
}
|
||||
|
||||
// For the rest, we figure out the size of the type and shallow copy them
|
||||
var value = EmitExpression(source);
|
||||
var destination = TmpName();
|
||||
|
||||
// Structs has known sizes at compile time
|
||||
if (complexType is StructTypeNode)
|
||||
if (source.Type.IsValueType)
|
||||
{
|
||||
var size = SizeOf(complexType);
|
||||
var size = SizeOf(source.Type);
|
||||
_writer.Indented($"{destination} =l alloc8 {size}");
|
||||
_writer.Indented($"blit {value}, {destination}, {size}");
|
||||
return destination;
|
||||
}
|
||||
// The rest of the complex types has unknown sizes
|
||||
else
|
||||
{
|
||||
var size = complexType switch
|
||||
{
|
||||
ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
|
||||
CStringTypeNode => EmitCStringSizeInBytes(value),
|
||||
StringTypeNode => EmitStringSizeInBytes(value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(source.Type))
|
||||
};
|
||||
|
||||
_writer.Indented($"{destination} =l alloc8 {size}");
|
||||
EmitMemcpy(value, destination, size);
|
||||
switch (source.Type)
|
||||
{
|
||||
case ArrayTypeNode arrayType:
|
||||
var arraySize = EmitArraySizeInBytes(arrayType, value);
|
||||
_writer.Indented($"{destination} =l alloc8 {arraySize}");
|
||||
EmitMemcpy(value, destination, arraySize);
|
||||
break;
|
||||
case CStringTypeNode:
|
||||
var cstrSize = EmitCStringSizeInBytes(value);
|
||||
_writer.Indented($"{destination} =l alloc8 {cstrSize}");
|
||||
EmitMemcpy(value, destination, cstrSize);
|
||||
break;
|
||||
case StringTypeNode:
|
||||
var strSize = EmitStringSizeInBytes(value);
|
||||
_writer.Indented($"{destination} =l alloc8 {strSize}");
|
||||
EmitMemcpy(value, destination, strSize);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Cannot copy type {source.Type}");
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
// Utility to create QBE type names for function parameters and return types
|
||||
private string FuncQBETypeName(TypeNode type)
|
||||
{
|
||||
if (type.IsSimpleType(out var simpleType, out var complexType))
|
||||
{
|
||||
return simpleType.StorageSize switch
|
||||
{
|
||||
StorageSize.I64 or StorageSize.U64 => "l",
|
||||
StorageSize.I32 or StorageSize.U32 => "w",
|
||||
StorageSize.I16 => "sh",
|
||||
StorageSize.I8 => "sb",
|
||||
StorageSize.U16 => "uh",
|
||||
StorageSize.U8 => "ub",
|
||||
StorageSize.F64 => "d",
|
||||
StorageSize.F32 => "s",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
if (complexType is StructTypeNode structType)
|
||||
{
|
||||
return StructTypeName(structType.Module, structType.Name);
|
||||
}
|
||||
|
||||
return "l";
|
||||
}
|
||||
|
||||
private void EmitStructType(StructTypeNode structType)
|
||||
{
|
||||
// todo(nub31): qbe expects structs to be declared in order. We must Check the dependencies of the struct to see if a type need to be declared before this one
|
||||
@@ -365,31 +404,6 @@ public class QBEGenerator
|
||||
}
|
||||
|
||||
_writer.WriteLine(" }");
|
||||
return;
|
||||
|
||||
string StructDefQBEType(TypeNode type)
|
||||
{
|
||||
if (type.IsSimpleType(out var simpleType, out var complexType))
|
||||
{
|
||||
return simpleType.StorageSize switch
|
||||
{
|
||||
StorageSize.I64 or StorageSize.U64 => "l",
|
||||
StorageSize.I32 or StorageSize.U32 => "w",
|
||||
StorageSize.I16 or StorageSize.U16 => "h",
|
||||
StorageSize.I8 or StorageSize.U8 => "b",
|
||||
StorageSize.F64 => "d",
|
||||
StorageSize.F32 => "s",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
if (complexType is StructTypeNode childStructType)
|
||||
{
|
||||
return StructTypeName(childStructType.Module, childStructType.Name);
|
||||
}
|
||||
|
||||
return "l";
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitStructDefinition(StructNode structDef)
|
||||
@@ -477,12 +491,6 @@ public class QBEGenerator
|
||||
|
||||
EmitBlock(funcDef.Body);
|
||||
|
||||
// Implicit return for void functions if no explicit return has been set
|
||||
if (funcDef.Signature.ReturnType is VoidTypeNode && funcDef.Body.Statements.LastOrDefault() is not ReturnNode)
|
||||
{
|
||||
_writer.Indented("ret");
|
||||
}
|
||||
|
||||
_writer.WriteLine("}");
|
||||
_writer.NewLine();
|
||||
}
|
||||
@@ -491,13 +499,8 @@ public class QBEGenerator
|
||||
{
|
||||
foreach (var statement in block.Statements)
|
||||
{
|
||||
if (_codeIsReachable)
|
||||
{
|
||||
EmitStatement(statement);
|
||||
}
|
||||
EmitStatement(statement);
|
||||
}
|
||||
|
||||
_codeIsReachable = true;
|
||||
}
|
||||
|
||||
private void EmitStatement(StatementNode statement)
|
||||
@@ -505,13 +508,13 @@ public class QBEGenerator
|
||||
switch (statement)
|
||||
{
|
||||
case AssignmentNode assignment:
|
||||
EmitAssignment(assignment);
|
||||
EmitCopyInto(assignment.Value, EmitAddressOf(assignment.Target));
|
||||
break;
|
||||
case BreakNode:
|
||||
EmitBreak();
|
||||
_writer.Indented($"jmp {_breakLabels.Peek()}");
|
||||
break;
|
||||
case ContinueNode:
|
||||
EmitContinue();
|
||||
_writer.Indented($"jmp {_continueLabels.Peek()}");
|
||||
break;
|
||||
case IfNode ifStatement:
|
||||
EmitIf(ifStatement);
|
||||
@@ -536,23 +539,6 @@ public class QBEGenerator
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitAssignment(AssignmentNode assignment)
|
||||
{
|
||||
EmitCopyInto(assignment.Value, EmitAddressOfLValue(assignment.Target));
|
||||
}
|
||||
|
||||
private void EmitBreak()
|
||||
{
|
||||
_writer.Indented($"jmp {_breakLabels.Peek()}");
|
||||
_codeIsReachable = false;
|
||||
}
|
||||
|
||||
private void EmitContinue()
|
||||
{
|
||||
_writer.Indented($"jmp {_continueLabels.Peek()}");
|
||||
_codeIsReachable = false;
|
||||
}
|
||||
|
||||
private void EmitIf(IfNode ifStatement)
|
||||
{
|
||||
var trueLabel = LabelName();
|
||||
@@ -666,60 +652,47 @@ public class QBEGenerator
|
||||
_writer.WriteLine(endLabel);
|
||||
}
|
||||
|
||||
private string EmitExpression(ExpressionNode expression)
|
||||
private string EmitExpression(ExpressionNode expr)
|
||||
{
|
||||
return expression switch
|
||||
return expr switch
|
||||
{
|
||||
ArrayInitializerNode arrayInitializer => EmitArrayInitializer(arrayInitializer),
|
||||
StructInitializerNode structInitializer => EmitStructInitializer(structInitializer),
|
||||
AddressOfNode addressOf => EmitAddressOf(addressOf),
|
||||
DereferenceNode dereference => EmitDereference(dereference),
|
||||
BinaryExpressionNode binary => EmitBinaryExpression(binary),
|
||||
FuncCallNode funcCall => EmitFuncCall(funcCall),
|
||||
ConvertIntNode convertInt => EmitConvertInt(convertInt),
|
||||
ConvertFloatNode convertFloat => EmitConvertFloat(convertFloat),
|
||||
VariableIdentifierNode identifier => EmitVariableIdentifier(identifier),
|
||||
FuncIdentifierNode funcIdentifier => EmitFuncIdentifier(funcIdentifier),
|
||||
FuncParameterIdentifierNode funcParameterIdentifier => EmitParameterFuncIdentifier(funcParameterIdentifier),
|
||||
LiteralNode literal => EmitLiteral(literal),
|
||||
UnaryExpressionNode unaryExpression => EmitUnaryExpression(unaryExpression),
|
||||
StructFieldAccessNode structFieldAccess => EmitStructFieldAccess(structFieldAccess),
|
||||
StructFuncCallNode structFuncCall => EmitStructFuncCall(structFuncCall),
|
||||
ArrayIndexAccessNode arrayIndex => EmitArrayIndexAccess(arrayIndex),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(expression))
|
||||
RValueExpressionNode rValue => EmitRValue(rValue),
|
||||
LValueExpressionNode lValue => EmitLValue(lValue),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(expr))
|
||||
};
|
||||
}
|
||||
|
||||
private string EmitFuncIdentifier(FuncIdentifierNode funcIdent)
|
||||
private string EmitRValue(RValueExpressionNode rValue)
|
||||
{
|
||||
return FuncName(funcIdent.Module, funcIdent.Name, funcIdent.ExternSymbol);
|
||||
return rValue switch
|
||||
{
|
||||
AddressOfNode expr => EmitAddressOf(expr.LValue),
|
||||
ArrayInitializerNode expr => EmitArrayInitializer(expr),
|
||||
BinaryExpressionNode expr => EmitBinaryExpression(expr),
|
||||
ConvertFloatNode expr => EmitConvertFloat(expr),
|
||||
ConvertIntNode expr => EmitConvertInt(expr),
|
||||
DereferenceNode expr => EmitLoad(expr.Type, EmitExpression(expr.Expression)),
|
||||
FuncCallNode expr => EmitFuncCall(expr),
|
||||
FuncIdentifierNode expr => FuncName(expr.Module, expr.Name, expr.ExternSymbol),
|
||||
FuncParameterIdentifierNode expr => $"%{expr.Name}",
|
||||
LiteralNode expr => EmitLiteral(expr),
|
||||
StructFuncCallNode expr => EmitStructFuncCall(expr),
|
||||
StructInitializerNode expr => EmitStructInitializer(expr),
|
||||
UnaryExpressionNode expr => EmitUnaryExpression(expr),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(rValue))
|
||||
};
|
||||
}
|
||||
|
||||
private string EmitVariableIdentifier(VariableIdentifierNode variableIdent)
|
||||
private string EmitLValue(LValueExpressionNode lValue)
|
||||
{
|
||||
var address = EmitAddressOfVariableIdent(variableIdent);
|
||||
if (variableIdent.Type is StructTypeNode)
|
||||
var address = EmitAddressOf(lValue);
|
||||
|
||||
if (lValue.Type is { IsValueType: true, IsScalar: false })
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
return EmitLoad(variableIdent.Type, address);
|
||||
}
|
||||
|
||||
private string EmitParameterFuncIdentifier(FuncParameterIdentifierNode funcParameterIdent)
|
||||
{
|
||||
return "%" + funcParameterIdent.Name;
|
||||
}
|
||||
|
||||
private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess)
|
||||
{
|
||||
var address = EmitAddressOfArrayIndexAccess(arrayIndexAccess);
|
||||
if (arrayIndexAccess.Type is StructTypeNode)
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
return EmitLoad(arrayIndexAccess.Type, address);
|
||||
return EmitLoad(lValue.Type, address);
|
||||
}
|
||||
|
||||
private string EmitArrayInitializer(ArrayInitializerNode arrayInitializer)
|
||||
@@ -743,30 +716,14 @@ public class QBEGenerator
|
||||
return arrayPointer;
|
||||
}
|
||||
|
||||
private string EmitDereference(DereferenceNode dereference)
|
||||
private string EmitAddressOf(LValueExpressionNode lval)
|
||||
{
|
||||
var address = EmitExpression(dereference.Expression);
|
||||
if (dereference.Type is StructTypeNode)
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
return EmitLoad(dereference.Type, address);
|
||||
}
|
||||
|
||||
private string EmitAddressOf(AddressOfNode addressOf)
|
||||
{
|
||||
return EmitAddressOfLValue(addressOf.LValue);
|
||||
}
|
||||
|
||||
private string EmitAddressOfLValue(LValueExpressionNode addressOf)
|
||||
{
|
||||
return addressOf switch
|
||||
return lval switch
|
||||
{
|
||||
ArrayIndexAccessNode arrayIndexAccess => EmitAddressOfArrayIndexAccess(arrayIndexAccess),
|
||||
StructFieldAccessNode structFieldAccess => EmitAddressOfStructFieldAccess(structFieldAccess),
|
||||
VariableIdentifierNode variableIdent => EmitAddressOfVariableIdent(variableIdent),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(addressOf))
|
||||
VariableIdentifierNode variableIdent => $"%{variableIdent.Name}",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(lval))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -794,11 +751,6 @@ public class QBEGenerator
|
||||
return address;
|
||||
}
|
||||
|
||||
private string EmitAddressOfVariableIdent(VariableIdentifierNode variableIdent)
|
||||
{
|
||||
return "%" + variableIdent.Name;
|
||||
}
|
||||
|
||||
private string EmitBinaryExpression(BinaryExpressionNode binaryExpression)
|
||||
{
|
||||
var left = EmitExpression(binaryExpression.Left);
|
||||
@@ -1116,17 +1068,6 @@ public class QBEGenerator
|
||||
throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported");
|
||||
}
|
||||
|
||||
private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccess)
|
||||
{
|
||||
var address = EmitAddressOfStructFieldAccess(structFieldAccess);
|
||||
if (structFieldAccess.Type is StructTypeNode)
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
return EmitLoad(structFieldAccess.Type, address);
|
||||
}
|
||||
|
||||
private string EmitStructFuncCall(StructFuncCallNode structFuncCall)
|
||||
{
|
||||
var func = StructFuncName(structFuncCall.StructType.Module, structFuncCall.StructType.Name, structFuncCall.Name);
|
||||
@@ -1236,19 +1177,16 @@ public class QBEGenerator
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
SimpleTypeNode simple => simple.StorageSize switch
|
||||
{
|
||||
StorageSize.Void => 0,
|
||||
StorageSize.I8 or StorageSize.U8 => 1,
|
||||
StorageSize.I16 or StorageSize.U16 => 2,
|
||||
StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4,
|
||||
StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}")
|
||||
},
|
||||
CStringTypeNode => 8,
|
||||
StringTypeNode => 8,
|
||||
ArrayTypeNode => 8,
|
||||
IntTypeNode intType => intType.Width / 8,
|
||||
FloatTypeNode fType => fType.Width / 8,
|
||||
BoolTypeNode => 1,
|
||||
PointerTypeNode => PTR_SIZE,
|
||||
FuncTypeNode => PTR_SIZE,
|
||||
StructTypeNode structType => CalculateStructSize(structType),
|
||||
VoidTypeNode => throw new InvalidOperationException("Void type has no size"),
|
||||
CStringTypeNode => PTR_SIZE,
|
||||
StringTypeNode => PTR_SIZE,
|
||||
ArrayTypeNode => PTR_SIZE,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
|
||||
};
|
||||
}
|
||||
@@ -1272,23 +1210,21 @@ public class QBEGenerator
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
SimpleTypeNode simple => simple.StorageSize switch
|
||||
{
|
||||
StorageSize.Void => 1,
|
||||
StorageSize.I8 or StorageSize.U8 => 1,
|
||||
StorageSize.I16 or StorageSize.U16 => 2,
|
||||
StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4,
|
||||
StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}")
|
||||
},
|
||||
CStringTypeNode => 8,
|
||||
StringTypeNode => 8,
|
||||
ArrayTypeNode => 8,
|
||||
StructTypeNode structType => CalculateStructAlignment(structType),
|
||||
IntTypeNode intType => intType.Width / 8,
|
||||
FloatTypeNode fType => fType.Width / 8,
|
||||
BoolTypeNode => 1,
|
||||
PointerTypeNode => PTR_SIZE,
|
||||
FuncTypeNode => PTR_SIZE,
|
||||
StructTypeNode st => CalculateStructAlignment(st),
|
||||
CStringTypeNode => PTR_SIZE,
|
||||
StringTypeNode => PTR_SIZE,
|
||||
ArrayTypeNode => PTR_SIZE,
|
||||
VoidTypeNode => throw new InvalidOperationException("Void has no alignment"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static int CalculateStructAlignment(StructTypeNode structType)
|
||||
{
|
||||
var maxAlignment = 1;
|
||||
@@ -1307,26 +1243,27 @@ public class QBEGenerator
|
||||
return (offset + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
private static int OffsetOf(StructTypeNode structDef, string member)
|
||||
private static int OffsetOf(StructTypeNode type, string member)
|
||||
{
|
||||
var offset = 0;
|
||||
|
||||
foreach (var field in structDef.Fields)
|
||||
foreach (var field in type.Fields)
|
||||
{
|
||||
var fieldAlignment = AlignmentOf(field.Type);
|
||||
offset = AlignTo(offset, fieldAlignment);
|
||||
|
||||
if (field.Name == member)
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
var fieldAlignment = AlignmentOf(field.Type);
|
||||
|
||||
offset = AlignTo(offset, fieldAlignment);
|
||||
offset += SizeOf(field.Type);
|
||||
}
|
||||
|
||||
throw new UnreachableException($"Member '{member}' not found in struct");
|
||||
}
|
||||
|
||||
|
||||
private string TmpName()
|
||||
{
|
||||
return $"%.t{++_tmpIndex}";
|
||||
|
||||
@@ -32,7 +32,7 @@ public enum BinaryOperator
|
||||
|
||||
public abstract record ExpressionNode(TypeNode Type) : Node;
|
||||
|
||||
public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type);
|
||||
public abstract record LValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
|
||||
|
||||
public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
|
||||
|
||||
|
||||
@@ -3,5 +3,3 @@
|
||||
public abstract record Node;
|
||||
|
||||
public record BlockNode(List<StatementNode> Statements) : Node;
|
||||
|
||||
public record TypedSyntaxTree(List<DefinitionNode> Definitions);
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace NubLang.TypeChecking.Node;
|
||||
|
||||
public record StatementNode : Node;
|
||||
public abstract record StatementNode : Node;
|
||||
|
||||
public record StatementExpressionNode(ExpressionNode Expression) : StatementNode;
|
||||
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NubLang.TypeChecking.Node;
|
||||
namespace NubLang.TypeChecking.Node;
|
||||
|
||||
public abstract class TypeNode : IEquatable<TypeNode>
|
||||
{
|
||||
public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType)
|
||||
{
|
||||
if (this is SimpleTypeNode st)
|
||||
{
|
||||
complexType = null;
|
||||
simpleType = st;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this is ComplexTypeNode ct)
|
||||
{
|
||||
complexType = ct;
|
||||
simpleType = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Type {this} is not a simple type nor a complex type");
|
||||
}
|
||||
public abstract bool IsValueType { get; }
|
||||
public abstract bool IsScalar { get; }
|
||||
|
||||
public override bool Equals(object? obj) => obj is TypeNode other && Equals(other);
|
||||
|
||||
public abstract bool Equals(TypeNode? other);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
public abstract override string ToString();
|
||||
|
||||
@@ -33,90 +15,71 @@ public abstract class TypeNode : IEquatable<TypeNode>
|
||||
public static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right);
|
||||
}
|
||||
|
||||
public enum StorageSize
|
||||
public class VoidTypeNode : TypeNode
|
||||
{
|
||||
Void,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
F32,
|
||||
F64
|
||||
public override bool IsValueType => false;
|
||||
public override bool IsScalar => false;
|
||||
|
||||
public override string ToString() => "void";
|
||||
public override bool Equals(TypeNode? other) => other is VoidTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
|
||||
}
|
||||
|
||||
public abstract class SimpleTypeNode : TypeNode
|
||||
public sealed class IntTypeNode(bool signed, int width) : TypeNode
|
||||
{
|
||||
public abstract StorageSize StorageSize { get; }
|
||||
}
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => true;
|
||||
|
||||
#region Simple types
|
||||
|
||||
public class IntTypeNode(bool signed, int width) : SimpleTypeNode
|
||||
{
|
||||
public bool Signed { get; } = signed;
|
||||
public int Width { get; } = width;
|
||||
|
||||
public override StorageSize StorageSize => Signed switch
|
||||
{
|
||||
true => Width switch
|
||||
{
|
||||
8 => StorageSize.I8,
|
||||
16 => StorageSize.I16,
|
||||
32 => StorageSize.I32,
|
||||
64 => StorageSize.I64,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
||||
},
|
||||
false => Width switch
|
||||
{
|
||||
8 => StorageSize.U8,
|
||||
16 => StorageSize.U16,
|
||||
32 => StorageSize.U32,
|
||||
64 => StorageSize.U64,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
||||
}
|
||||
};
|
||||
|
||||
public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
|
||||
public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(IntTypeNode), Signed, Width);
|
||||
}
|
||||
|
||||
public class FloatTypeNode(int width) : SimpleTypeNode
|
||||
public sealed class FloatTypeNode(int width) : TypeNode
|
||||
{
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => true;
|
||||
|
||||
public int Width { get; } = width;
|
||||
|
||||
public override StorageSize StorageSize => Width switch
|
||||
{
|
||||
32 => StorageSize.F32,
|
||||
64 => StorageSize.F64,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
||||
};
|
||||
|
||||
public override string ToString() => $"f{Width}";
|
||||
public override bool Equals(TypeNode? other) => other is FloatTypeNode @int && @int.Width == Width;
|
||||
public override bool Equals(TypeNode? other) => other is FloatTypeNode @float && @float.Width == Width;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(FloatTypeNode), Width);
|
||||
}
|
||||
|
||||
public class BoolTypeNode : SimpleTypeNode
|
||||
public class BoolTypeNode : TypeNode
|
||||
{
|
||||
public override StorageSize StorageSize => StorageSize.U8;
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => true;
|
||||
|
||||
public override string ToString() => "bool";
|
||||
public override bool Equals(TypeNode? other) => other is BoolTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoolTypeNode));
|
||||
}
|
||||
|
||||
public class FuncTypeNode(List<TypeNode> parameters, TypeNode returnType) : SimpleTypeNode
|
||||
public sealed class PointerTypeNode(TypeNode baseType) : TypeNode
|
||||
{
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => true;
|
||||
|
||||
public TypeNode BaseType { get; } = baseType;
|
||||
|
||||
public override string ToString() => "^" + BaseType;
|
||||
public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType);
|
||||
}
|
||||
|
||||
public class FuncTypeNode(List<TypeNode> parameters, TypeNode returnType) : TypeNode
|
||||
{
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => true;
|
||||
|
||||
public List<TypeNode> Parameters { get; } = parameters;
|
||||
public TypeNode ReturnType { get; } = returnType;
|
||||
|
||||
public override StorageSize StorageSize => StorageSize.U64;
|
||||
|
||||
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
||||
public override bool Equals(TypeNode? other) => other is FuncTypeNode func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
|
||||
|
||||
@@ -134,44 +97,19 @@ public class FuncTypeNode(List<TypeNode> parameters, TypeNode returnType) : Simp
|
||||
}
|
||||
}
|
||||
|
||||
public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode
|
||||
public class StructTypeNode(string module, string name, List<StructTypeField> fields, List<StructTypeFunc> functions) : TypeNode
|
||||
{
|
||||
public TypeNode BaseType { get; } = baseType;
|
||||
public override bool IsValueType => true;
|
||||
public override bool IsScalar => false;
|
||||
|
||||
public override StorageSize StorageSize => StorageSize.U64;
|
||||
public string Module { get; } = module;
|
||||
public string Name { get; } = name;
|
||||
public List<StructTypeField> Fields { get; set; } = fields;
|
||||
public List<StructTypeFunc> Functions { get; set; } = functions;
|
||||
|
||||
public override string ToString() => "^" + BaseType;
|
||||
public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType);
|
||||
}
|
||||
|
||||
public class VoidTypeNode : SimpleTypeNode
|
||||
{
|
||||
public override StorageSize StorageSize => StorageSize.Void;
|
||||
|
||||
public override string ToString() => "void";
|
||||
public override bool Equals(TypeNode? other) => other is VoidTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract class ComplexTypeNode : TypeNode;
|
||||
|
||||
#region Complex types
|
||||
|
||||
public class CStringTypeNode : ComplexTypeNode
|
||||
{
|
||||
public override string ToString() => "cstring";
|
||||
public override bool Equals(TypeNode? other) => other is CStringTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode));
|
||||
}
|
||||
|
||||
public class StringTypeNode : ComplexTypeNode
|
||||
{
|
||||
public override string ToString() => "string";
|
||||
public override bool Equals(TypeNode? other) => other is StringTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
|
||||
public override string ToString() => Name;
|
||||
public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
|
||||
}
|
||||
|
||||
public class StructTypeField(string name, TypeNode type, bool hasDefaultValue)
|
||||
@@ -187,26 +125,34 @@ public class StructTypeFunc(string name, FuncTypeNode type)
|
||||
public FuncTypeNode Type { get; } = type;
|
||||
}
|
||||
|
||||
public class StructTypeNode(string module, string name, List<StructTypeField> fields, List<StructTypeFunc> functions) : ComplexTypeNode
|
||||
public class CStringTypeNode : TypeNode
|
||||
{
|
||||
public string Module { get; } = module;
|
||||
public string Name { get; } = name;
|
||||
public List<StructTypeField> Fields { get; set; } = fields;
|
||||
public List<StructTypeFunc> Functions { get; set; } = functions;
|
||||
public override bool IsValueType => false;
|
||||
public override bool IsScalar => false;
|
||||
|
||||
public override string ToString() => Name;
|
||||
public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
|
||||
public override string ToString() => "cstring";
|
||||
public override bool Equals(TypeNode? other) => other is CStringTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode));
|
||||
}
|
||||
|
||||
public class ArrayTypeNode(TypeNode elementType) : ComplexTypeNode
|
||||
public class StringTypeNode : TypeNode
|
||||
{
|
||||
public override bool IsValueType => false;
|
||||
public override bool IsScalar => false;
|
||||
|
||||
public override string ToString() => "string";
|
||||
public override bool Equals(TypeNode? other) => other is StringTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
|
||||
}
|
||||
|
||||
public class ArrayTypeNode(TypeNode elementType) : TypeNode
|
||||
{
|
||||
public override bool IsValueType => false;
|
||||
public override bool IsScalar => false;
|
||||
|
||||
public TypeNode ElementType { get; } = elementType;
|
||||
|
||||
public override string ToString() => "[]" + ElementType;
|
||||
|
||||
public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), ElementType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -117,15 +117,25 @@ public sealed class TypeChecker
|
||||
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
||||
}
|
||||
|
||||
var signature = CheckFuncSignature(node.Signature);
|
||||
|
||||
BlockNode? body = null;
|
||||
if (node.Body != null)
|
||||
{
|
||||
_funcReturnTypes.Push(ResolveType(node.Signature.ReturnType));
|
||||
_funcReturnTypes.Push(signature.ReturnType);
|
||||
|
||||
body = CheckBlock(node.Body, scope);
|
||||
|
||||
// Insert implicit return for void functions
|
||||
if (signature.ReturnType is VoidTypeNode && body.Statements.LastOrDefault() is not ReturnNode)
|
||||
{
|
||||
body.Statements.Add(new ReturnNode(Optional<ExpressionNode>.Empty()));
|
||||
}
|
||||
|
||||
_funcReturnTypes.Pop();
|
||||
}
|
||||
|
||||
return new FuncNode(_syntaxTree.Metadata.ModuleName, node.Name, node.ExternSymbol, CheckFuncSignature(node.Signature), body);
|
||||
return new FuncNode(_syntaxTree.Metadata.ModuleName, node.Name, node.ExternSymbol, signature, body);
|
||||
}
|
||||
|
||||
private StatementNode CheckStatement(StatementSyntax node)
|
||||
@@ -715,9 +725,29 @@ public sealed class TypeChecker
|
||||
|
||||
_scopes.Push(scope ?? Scope.SubScope());
|
||||
|
||||
var reachable = true;
|
||||
var warnedUnreachable = false;
|
||||
|
||||
foreach (var statement in node.Statements)
|
||||
{
|
||||
statements.Add(CheckStatement(statement));
|
||||
var checkedStatement = CheckStatement(statement);
|
||||
|
||||
if (reachable)
|
||||
{
|
||||
statements.Add(checkedStatement);
|
||||
if (checkedStatement is ReturnNode or BreakNode or ContinueNode)
|
||||
{
|
||||
reachable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!warnedUnreachable)
|
||||
{
|
||||
Diagnostics.Add(Diagnostic.Warning("Statement is unreachable").At(statement).Build());
|
||||
warnedUnreachable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_scopes.Pop();
|
||||
|
||||
@@ -2,6 +2,11 @@ module "main"
|
||||
|
||||
extern "puts" func puts(text: cstring)
|
||||
|
||||
struct Human
|
||||
{
|
||||
name: cstring
|
||||
}
|
||||
|
||||
extern "main" func main(args: []cstring): i64
|
||||
{
|
||||
let x = [2]cstring
|
||||
@@ -14,5 +19,16 @@ extern "main" func main(args: []cstring): i64
|
||||
puts(u)
|
||||
}
|
||||
|
||||
let me: Human = {
|
||||
name = "test"
|
||||
}
|
||||
|
||||
puts(me.name)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
func test()
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user