diff --git a/projects/compiler/example.abra b/projects/compiler/example.abra index 537af5cf..1a73c7fa 100644 --- a/projects/compiler/example.abra +++ b/projects/compiler/example.abra @@ -1 +1,11 @@ -println(1) \ No newline at end of file +val x = 'a' +println(x) + +val t = x == 'a' +val f = x == 'A' +println(t, f) + +val h = 'a'.hash() +println(h) + +println(x.asInt()) diff --git a/projects/compiler/src/compiler.abra b/projects/compiler/src/compiler.abra index 3a4f7ff8..d977c4b2 100644 --- a/projects/compiler/src/compiler.abra +++ b/projects/compiler/src/compiler.abra @@ -1873,6 +1873,7 @@ export type Compiler { LiteralAstNode.Int(v) => (Value.Int(v), Type(kind: TypeKind.PrimitiveInt)) LiteralAstNode.Float(f) => (Value.Float(f), Type(kind: TypeKind.PrimitiveFloat)) LiteralAstNode.Bool(b) => (Value.Int(if b 1 else 0), Type(kind: TypeKind.PrimitiveBool)) + LiteralAstNode.Char(c) => (Value.IntU64(c), Type(kind: TypeKind.PrimitiveChar)) LiteralAstNode.String(s) => { val dataPtr = self._builder.buildGlobalString(s.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n").replaceAll("\r", "\\r")) val instancePtr = try self._constructString(dataPtr, Value.Int(s.length)) @@ -2266,7 +2267,7 @@ export type Compiler { } func _compileEqLogic(self, leftVal: Value, rightVal: Value, ty: Type, position: Position, localName: String? = None, negate = false): Result { - if self._typeIsInt(ty) || self._typeIsFloat(ty) || self._typeIsBool(ty) { + if self._typeIsInt(ty) || self._typeIsFloat(ty) || self._typeIsBool(ty) || self._typeIsChar(ty) { val res = if negate { val res = try self._currentFn.block.buildCompareNeq(leftVal, rightVal, localName) else |e| return qbeError(e) self._currentFn.block.buildExt(res, false) @@ -2416,6 +2417,7 @@ export type Compiler { TypeKind.PrimitiveInt => Ok(Some(QbeType.U64)) TypeKind.PrimitiveFloat => Ok(Some(QbeType.F64)) TypeKind.PrimitiveBool => Ok(Some(QbeType.U64)) + TypeKind.PrimitiveChar => Ok(Some(QbeType.U64)) TypeKind.PrimitiveString => Ok(Some(QbeType.Pointer)) TypeKind.Never => Ok(None) TypeKind.Any => todo("TypeKind.Any") @@ -3079,6 +3081,17 @@ export type Compiler { Ok(str) } + "char_as_int" => { + self._currentFn.block.addComment("begin char_as_int...") + + val _arg = if arguments[0] |arg| arg else unreachable("'char_as_int' has 1 required argument") + val arg = if _arg |arg| arg else unreachable("'char_as_int' has 1 required argument") + val argVal = try self._compileExpression(arg) + + self._currentFn.block.addComment("...char_as_int end") + + Ok(argVal) + } "int_as_float" => { self._currentFn.block.addComment("begin int_as_float...") @@ -3389,6 +3402,7 @@ export type Compiler { if struct == self._project.preludeFloatStruct return self._getOrCompileFloatToStringMethod() if struct == self._project.preludeStringStruct return self._getOrCompileStringToStringMethod() if struct == self._project.preludeBoolStruct return self._getOrCompileBoolToStringMethod() + if struct == self._project.preludeCharStruct return self._getOrCompileCharToStringMethod() val _fn = struct.instanceMethods.find(m => m.label.name == "toString") val fn = if _fn |f| f else unreachable("every struct has a toString method defined") @@ -3699,6 +3713,35 @@ export type Compiler { Ok(fnVal) } + func _getOrCompileCharToStringMethod(self): Result { + val charTypeQbe = try self._getQbeTypeForTypeExpect(Type(kind: TypeKind.PrimitiveChar), "char qbe type should exist") + val stringTypeQbe = try self._getQbeTypeForTypeExpect(Type(kind: TypeKind.PrimitiveString), "string qbe type should exist") + + val typeName = try self._structTypeName(self._project.preludeCharStruct) + val methodName = "$typeName..toString" + if self._builder.getFunction(methodName) |fn| return Ok(fn) + + val fnVal = self._builder.buildFunction(name: methodName, returnType: Some(stringTypeQbe)) + val prevFn = self._currentFn + self._currentFn = fnVal + + fnVal.addComment("Char#toString(self): String") + val selfParam = fnVal.addParameter("self", stringTypeQbe) + + val lowestByteVal = try self._currentFn.block.buildAnd(selfParam, Value.Int(0xff)) else |e| return qbeError(e) + val strData = try self._callMalloc(Value.Int(1)) + self._currentFn.block.buildStoreL(lowestByteVal, strData) + + val strVal = try self._constructString(strData, Value.Int(1)) + fnVal.block.buildReturn(Some(strVal)) + + try fnVal.block.verify() else |e| return qbeError(e) + + self._currentFn = prevFn + + Ok(fnVal) + } + func _getOrCompileStringToStringMethod(self): Result { val stringTypeQbe = try self._getQbeTypeForTypeExpect(Type(kind: TypeKind.PrimitiveString), "string qbe type should exist") @@ -3792,6 +3835,28 @@ export type Compiler { return Ok(fnVal) } + if struct == self._project.preludeCharStruct { + val charTypeQbe = try self._getQbeTypeForTypeExpect(Type(kind: TypeKind.PrimitiveChar), "char qbe type should exist") + + val methodName = "Char..eq" + if self._builder.getFunction(methodName) |fn| return Ok(fn) + + val fnVal = self._builder.buildFunction(name: methodName, returnType: Some(boolTypeQbe)) + val prevFn = self._currentFn + self._currentFn = fnVal + + fnVal.addComment("Char#eq(self, other: Char): Char") + val selfParam = fnVal.addParameter("self", charTypeQbe) + val otherParam = fnVal.addParameter("other", charTypeQbe) + val res = try self._currentFn.block.buildCompareEq(selfParam, otherParam) else |e| return qbeError(e) + fnVal.block.buildReturn(Some(self._currentFn.block.buildExt(res, false))) + + try fnVal.block.verify() else |e| return qbeError(e) + + self._currentFn = prevFn + + return Ok(fnVal) + } val _fn = struct.instanceMethods.find(m => m.label.name == "eq") val fn = if _fn |f| f else unreachable("every struct has an eq method defined") @@ -4007,6 +4072,26 @@ export type Compiler { return Ok(fnVal) } + if struct == self._project.preludeCharStruct { + val charTypeQbe = try self._getQbeTypeForTypeExpect(Type(kind: TypeKind.PrimitiveChar), "char qbe type should exist") + + val methodName = "Char..hash" + if self._builder.getFunction(methodName) |fn| return Ok(fn) + + val fnVal = self._builder.buildFunction(name: methodName, returnType: Some(intTypeQbe)) + val prevFn = self._currentFn + self._currentFn = fnVal + + fnVal.addComment("Charhash(self): Int") + val selfParam = fnVal.addParameter("self", charTypeQbe) + fnVal.block.buildReturn(Some(selfParam)) + + try fnVal.block.verify() else |e| return qbeError(e) + + self._currentFn = prevFn + + return Ok(fnVal) + } val _fn = struct.instanceMethods.find(m => m.label.name == "hash") val fn = if _fn |f| f else unreachable("every struct has a hash method defined") @@ -4296,6 +4381,7 @@ export type Compiler { TypeKind.PrimitiveInt => Ok((StructOrEnum.Struct(self._project.preludeIntStruct), [])) TypeKind.PrimitiveFloat => Ok((StructOrEnum.Struct(self._project.preludeFloatStruct), [])) TypeKind.PrimitiveBool => Ok((StructOrEnum.Struct(self._project.preludeBoolStruct), [])) + TypeKind.PrimitiveChar => Ok((StructOrEnum.Struct(self._project.preludeCharStruct), [])) TypeKind.PrimitiveString => Ok((StructOrEnum.Struct(self._project.preludeStringStruct), [])) TypeKind.Never => unreachable("getInstanceTypeForType: Never") TypeKind.Generic(name) => { @@ -4629,6 +4715,7 @@ export type Compiler { func _typeIsInt(self, ty: Type): Bool = ty.kind == TypeKind.PrimitiveInt || ty.kind == TypeKind.Instance(StructOrEnum.Struct(self._project.preludeIntStruct), []) func _typeIsFloat(self, ty: Type): Bool = ty.kind == TypeKind.PrimitiveFloat || ty.kind == TypeKind.Instance(StructOrEnum.Struct(self._project.preludeFloatStruct), []) func _typeIsBool(self, ty: Type): Bool = ty.kind == TypeKind.PrimitiveBool || ty.kind == TypeKind.Instance(StructOrEnum.Struct(self._project.preludeBoolStruct), []) + func _typeIsChar(self, ty: Type): Bool = ty.kind == TypeKind.PrimitiveChar || ty.kind == TypeKind.Instance(StructOrEnum.Struct(self._project.preludeCharStruct), []) func _typeIsString(self, ty: Type): Bool = ty.kind == TypeKind.PrimitiveString || ty.kind == TypeKind.Instance(StructOrEnum.Struct(self._project.preludeStringStruct), []) // TODO: this is copied from Typechecker diff --git a/projects/compiler/src/lexer.abra b/projects/compiler/src/lexer.abra index f600c720..84f32ed2 100644 --- a/projects/compiler/src/lexer.abra +++ b/projects/compiler/src/lexer.abra @@ -12,6 +12,7 @@ export enum TokenKind { Int(value: Int) Float(value: Float) Bool(value: Bool) + Char(ch: Int) String(value: String) StringInterpolation(chunks: StringInterpolationChunk[]) @@ -89,6 +90,7 @@ export enum TokenKind { TokenKind.Bool(value) => value.toString() TokenKind.String => "string" TokenKind.StringInterpolation => "string" + TokenKind.Char => "char" TokenKind.Ident(name) => if name == "_" { "_" } else "identifier" TokenKind.If => "if" TokenKind.Else => "else" @@ -251,6 +253,8 @@ export type Lexer { try self._tokenizeInteger(startPos: position) } else if ch == "\"" { try self._tokenizeString() + } else if ch == "'" { + try self._tokenizeChar() } else if ch.isAlpha() || ch == "_" { self._tokenizeIdentifier(startPos: position) } else if ch == "/" && (peek == "/" || peek == "*") { @@ -435,6 +439,27 @@ export type Lexer { Ok(Token(position: startPos, kind: TokenKind.Float(float))) } + func _tokenizeChar(self): Result { + var startPos = self._curPos() + self._advance() // consume "'" + + var ch = self._input[self._cursor] + if ch == "'" return Err(LexerError(position: self._curPos(), kind: LexerErrorKind.UnexpectedChar(ch))) + if ch == "\\" todo("character escape sequences") + + // TODO: once characters are supported, there should be no need for low-level byte manipulation like this + val intVal = ch._buffer.offset(0).load().asInt() + self._advance() + + ch = self._input[self._cursor] + if ch != "'" { + return Err(LexerError(position: self._curPos(), kind: LexerErrorKind.UnexpectedChar(ch))) + } + self._advance() // consume "'" + + Ok(Token(position: startPos, kind: TokenKind.Char(intVal))) + } + func _tokenizeString(self): Result { var startPos = self._curPos() val origStartPos = startPos diff --git a/projects/compiler/src/parser.abra b/projects/compiler/src/parser.abra index b170ef9a..0642599b 100644 --- a/projects/compiler/src/parser.abra +++ b/projects/compiler/src/parser.abra @@ -25,6 +25,7 @@ export enum LiteralAstNode { Int(value: Int) Float(value: Float) Bool(value: Bool) + Char(value: Int) String(value: String) } @@ -1092,6 +1093,7 @@ export type Parser { TokenKind.Int => self._parseLiteral() TokenKind.Float => self._parseLiteral() TokenKind.Bool => self._parseLiteral() + TokenKind.Char => self._parseLiteral() TokenKind.String => self._parseLiteral() TokenKind.StringInterpolation => self._parseStringInterpolation() TokenKind.LParen => self._parseGroupedOrTupleOrLambda() @@ -1129,6 +1131,7 @@ export type Parser { TokenKind.Int(value) => LiteralAstNode.Int(value) TokenKind.Float(value) => LiteralAstNode.Float(value) TokenKind.Bool(value) => LiteralAstNode.Bool(value) + TokenKind.Char(value) => LiteralAstNode.Char(value) TokenKind.String(value) => LiteralAstNode.String(value) TokenKind.StringInterpolation => { return Err(ParseError(position: token.position, kind: ParseErrorKind.NotYetImplemented)) @@ -1313,6 +1316,7 @@ export type Parser { val items = try self._commaSeparated(end: TokenKind.RBrace, consumeFinal: true, fn: () => { val keyToken = try self._expectPeek() val key = match keyToken.kind { + TokenKind.Char => try self._parseExpression() TokenKind.String => try self._parseExpression() TokenKind.Ident => try self._parseExpression() TokenKind.LParen => { @@ -1391,6 +1395,10 @@ export type Parser { self._advance() // consume bool token MatchCaseKind.Literal(LiteralAstNode.Bool(value)) } + TokenKind.Char(value) => { + self._advance() // consume char token + MatchCaseKind.Literal(LiteralAstNode.Char(value)) + } TokenKind.String(value) => { self._advance() // consume string token MatchCaseKind.Literal(LiteralAstNode.String(value)) diff --git a/projects/compiler/src/test_utils.abra b/projects/compiler/src/test_utils.abra index 75cbaf33..1ad38d1f 100644 --- a/projects/compiler/src/test_utils.abra +++ b/projects/compiler/src/test_utils.abra @@ -12,6 +12,22 @@ export func printTokenAsJson(token: Token, indentLevelStart: Int, currentIndentL print("$endIndent}") } +export func charToString(charVal: Int): String { + // TODO: clean this up + val lowestByte = charVal && 0xff + + val higherBytes = [ + charVal && 0xff00, + charVal && 0xff0000, + charVal && 0xff000000, + ] + for b, idx in higherBytes { + if b != 0 unreachable("byte $idx of Char($charVal) is not 0") + } + + lowestByte.hex() +} + func printTokenKindAsJson(kind: TokenKind, indentLevelStart: Int, currentIndentLevel: Int) { val startIndent = " ".repeat(indentLevelStart) val fieldsIndent = " ".repeat(currentIndentLevel + 1) @@ -31,6 +47,12 @@ func printTokenKindAsJson(kind: TokenKind, indentLevelStart: Int, currentIndentL println("$fieldsIndent\"name\": \"Bool\",") println("$fieldsIndent\"value\": $value") } + TokenKind.Char(intVal) => { + val charAsString = charToString(intVal) + + println("$fieldsIndent\"name\": \"Char\",") + println("$fieldsIndent\"value\": \"$charAsString\"") + } TokenKind.String(value) => { println("$fieldsIndent\"name\": \"String\",") println("$fieldsIndent\"value\": \"$value\"") @@ -341,6 +363,11 @@ func printAstNodeKindAsJson(kind: AstNodeKind, indentLevelStart: Int, currentInd LiteralAstNode.Int(value) => ("int", value.toString()) LiteralAstNode.Float(value) => ("float", value.toString()) LiteralAstNode.Bool(value) => ("bool", value.toString()) + LiteralAstNode.Char(value) => { + val charAsString = charToString(value) + + ("char", "\"$charAsString\"") + } LiteralAstNode.String(value) => ("string", "\"$value\"") } @@ -664,6 +691,10 @@ func printAstNodeKindAsJson(kind: AstNodeKind, indentLevelStart: Int, currentInd LiteralAstNode.Int(value) => ("int", value.toString()) LiteralAstNode.Float(value) => ("float", value.toString()) LiteralAstNode.Bool(value) => ("bool", value.toString()) + LiteralAstNode.Char(value) => { + val charAsString = charToString(value) + ("char", "\"$charAsString\"") + } LiteralAstNode.String(value) => ("string", "\"$value\"") } diff --git a/projects/compiler/src/typechecker.abra b/projects/compiler/src/typechecker.abra index 0dbb465b..13fa2d76 100644 --- a/projects/compiler/src/typechecker.abra +++ b/projects/compiler/src/typechecker.abra @@ -267,6 +267,7 @@ export type Project { preludeIntStruct: Struct = Struct(moduleId: 0, label: Label(name: "Int", position: Position(line: 0, col: 0)), scope: Scope(name: "Int")) preludeFloatStruct: Struct = Struct(moduleId: 0, label: Label(name: "Float", position: Position(line: 0, col: 0)), scope: Scope(name: "Float")) preludeBoolStruct: Struct = Struct(moduleId: 0, label: Label(name: "Bool", position: Position(line: 0, col: 0)), scope: Scope(name: "Bool")) + preludeCharStruct: Struct = Struct(moduleId: 0, label: Label(name: "Char", position: Position(line: 0, col: 0)), scope: Scope(name: "Char")) preludeStringStruct: Struct = Struct(moduleId: 0, label: Label(name: "String", position: Position(line: 0, col: 0)), scope: Scope(name: "String")) preludeArrayStruct: Struct = Struct(moduleId: 0, label: Label(name: "Array", position: Position(line: 0, col: 0)), scope: Scope(name: "Array"), typeParams: ["T"]) preludeMapStruct: Struct = Struct(moduleId: 0, label: Label(name: "Map", position: Position(line: 0, col: 0)), scope: Scope(name: "Array"), typeParams: ["K", "V"]) @@ -296,6 +297,11 @@ export type Project { TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.preludeBoolStruct) _ => false } + TypeKind.PrimitiveChar => match other.kind { + TypeKind.PrimitiveChar => true + TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.preludeCharStruct) + _ => false + } TypeKind.PrimitiveString => match other.kind { TypeKind.PrimitiveString => true TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.preludeStringStruct) @@ -334,6 +340,7 @@ export type Project { TypeKind.PrimitiveInt => reqStructOrEnum == StructOrEnum.Struct(self.preludeIntStruct) TypeKind.PrimitiveFloat => reqStructOrEnum == StructOrEnum.Struct(self.preludeFloatStruct) TypeKind.PrimitiveBool => reqStructOrEnum == StructOrEnum.Struct(self.preludeBoolStruct) + TypeKind.PrimitiveChar => reqStructOrEnum == StructOrEnum.Struct(self.preludeCharStruct) TypeKind.PrimitiveString => reqStructOrEnum == StructOrEnum.Struct(self.preludeStringStruct) _ => false } @@ -364,6 +371,7 @@ export type Type { TypeKind.PrimitiveInt => "Int" TypeKind.PrimitiveFloat => "Float" TypeKind.PrimitiveBool => "Bool" + TypeKind.PrimitiveChar => "Char" TypeKind.PrimitiveString => "String" TypeKind.Never => "Never" TypeKind.Generic(name) => name @@ -415,6 +423,7 @@ export type Type { TypeKind.PrimitiveInt => false TypeKind.PrimitiveFloat => false TypeKind.PrimitiveBool => false + TypeKind.PrimitiveChar => false TypeKind.PrimitiveString => false TypeKind.Never => false TypeKind.Generic => false @@ -569,6 +578,7 @@ export enum TypeKind { PrimitiveInt PrimitiveFloat PrimitiveBool + PrimitiveChar PrimitiveString Never Any @@ -1473,6 +1483,7 @@ export type Typechecker { self.project.preludeIntStruct, self.project.preludeFloatStruct, self.project.preludeBoolStruct, + self.project.preludeCharStruct, self.project.preludeStringStruct, self.project.preludeArrayStruct, self.project.preludeMapStruct, @@ -1651,6 +1662,7 @@ export type Typechecker { "Int" => Ok(Type(kind: TypeKind.PrimitiveInt)) "Float" => Ok(Type(kind: TypeKind.PrimitiveFloat)) "Bool" => Ok(Type(kind: TypeKind.PrimitiveBool)) + "Char" => Ok(Type(kind: TypeKind.PrimitiveChar)) "String" => Ok(Type(kind: TypeKind.PrimitiveString)) "Map" => Ok(Type(kind: TypeKind.Type(StructOrEnum.Struct(self.project.preludeMapStruct)))) "Set" => Ok(Type(kind: TypeKind.Type(StructOrEnum.Struct(self.project.preludeSetStruct)))) @@ -1916,6 +1928,11 @@ export type Typechecker { TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.project.preludeBoolStruct) _ => false } + TypeKind.PrimitiveChar => match ty.kind { + TypeKind.PrimitiveChar => true + TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.project.preludeCharStruct) + _ => false + } TypeKind.PrimitiveString => match ty.kind { TypeKind.PrimitiveString => true TypeKind.Instance(structOrEnum, _) => structOrEnum == StructOrEnum.Struct(self.project.preludeStringStruct) @@ -1955,6 +1972,7 @@ export type Typechecker { TypeKind.PrimitiveInt => reqStructOrEnum == StructOrEnum.Struct(self.project.preludeIntStruct) TypeKind.PrimitiveFloat => reqStructOrEnum == StructOrEnum.Struct(self.project.preludeFloatStruct) TypeKind.PrimitiveBool => reqStructOrEnum == StructOrEnum.Struct(self.project.preludeBoolStruct) + TypeKind.PrimitiveChar => reqStructOrEnum == StructOrEnum.Struct(self.project.preludeCharStruct) TypeKind.PrimitiveString => reqStructOrEnum == StructOrEnum.Struct(self.project.preludeStringStruct) _ => false } @@ -2844,6 +2862,7 @@ export type Typechecker { "Int" => self.project.preludeIntStruct = struct "Float" => self.project.preludeFloatStruct = struct "Bool" => self.project.preludeBoolStruct = struct + "Char" => self.project.preludeCharStruct = struct "String" => self.project.preludeStringStruct = struct "Array" => self.project.preludeArrayStruct = struct "Set" => self.project.preludeSetStruct = struct @@ -3629,6 +3648,7 @@ export type Typechecker { LiteralAstNode.Int => Type(kind: TypeKind.PrimitiveInt) LiteralAstNode.Float => Type(kind: TypeKind.PrimitiveFloat) LiteralAstNode.Bool => Type(kind: TypeKind.PrimitiveBool) + LiteralAstNode.Char => Type(kind: TypeKind.PrimitiveChar) LiteralAstNode.String => Type(kind: TypeKind.PrimitiveString) } @@ -3745,6 +3765,7 @@ export type Typechecker { LiteralAstNode.Int => TypeKind.PrimitiveInt LiteralAstNode.Float => TypeKind.PrimitiveFloat LiteralAstNode.Bool => TypeKind.PrimitiveBool + LiteralAstNode.Char => TypeKind.PrimitiveChar LiteralAstNode.String => TypeKind.PrimitiveString } @@ -4000,6 +4021,7 @@ export type Typechecker { TypeKind.PrimitiveInt => return self._resolveAccessorPathSegment(Type(kind: TypeKind.Instance(StructOrEnum.Struct(self.project.preludeIntStruct), [])), label, None, optSafe) TypeKind.PrimitiveFloat => return self._resolveAccessorPathSegment(Type(kind: TypeKind.Instance(StructOrEnum.Struct(self.project.preludeFloatStruct), [])), label, None, optSafe) TypeKind.PrimitiveBool => return self._resolveAccessorPathSegment(Type(kind: TypeKind.Instance(StructOrEnum.Struct(self.project.preludeBoolStruct), [])), label, None, optSafe) + TypeKind.PrimitiveChar => return self._resolveAccessorPathSegment(Type(kind: TypeKind.Instance(StructOrEnum.Struct(self.project.preludeCharStruct), [])), label, None, optSafe) TypeKind.PrimitiveString => return self._resolveAccessorPathSegment(Type(kind: TypeKind.Instance(StructOrEnum.Struct(self.project.preludeStringStruct), [])), label, None, optSafe) TypeKind.Never => None TypeKind.Generic => self._resolveAccessorPathSegmentAny(label, optSafe, typeHint) diff --git a/projects/compiler/src/typechecker_test_utils.abra b/projects/compiler/src/typechecker_test_utils.abra index f071705f..e47338d4 100644 --- a/projects/compiler/src/typechecker_test_utils.abra +++ b/projects/compiler/src/typechecker_test_utils.abra @@ -1,6 +1,6 @@ import LiteralAstNode, IndexingMode from "./parser" import Type, TypeKind, TypedModule, TypedAstNode, TypedAstNodeKind, Variable, Function, FunctionKind, Scope, Struct, Enum, StructOrEnum, AccessorPathSegment, TypedInvokee, TypedIndexingNode, TypedAssignmentMode, Field, EnumVariantKind, Export, TypedMatchCaseKind from "./typechecker" -import printTokenAsJson, printLabelAsJson, printBindingPatternAsJson from "./test_utils" +import printTokenAsJson, printLabelAsJson, printBindingPatternAsJson, charToString from "./test_utils" export type Jsonifier { indentLevel: Int = 0 @@ -331,6 +331,7 @@ export type Jsonifier { LiteralAstNode.Int(value) => self.println("\"value\": $value") LiteralAstNode.Float(value) => self.println("\"value\": $value") LiteralAstNode.Bool(value) => self.println("\"value\": $value") + LiteralAstNode.Char(value) => self.println("\"value\": \"${charToString(value)}\"") LiteralAstNode.String(value) => self.println("\"value\": \"$value\"") } } diff --git a/projects/compiler/test/compiler/chars.abra b/projects/compiler/test/compiler/chars.abra new file mode 100644 index 00000000..ef840076 --- /dev/null +++ b/projects/compiler/test/compiler/chars.abra @@ -0,0 +1,14 @@ +val a = 'a' +val b = 'b' + +/// Expect: a b +println(a, b) + +/// Expect: false true +println(a == b, a == 'a') +/// Expect: false true +println(b != b, b != 'a') + +val m = { 'a': 1, 'b': 2 } +/// Expect: { a: 1, b: 2 } Option.None Option.Some(value: 1) +println(m, m['c'], m['a']) diff --git a/projects/compiler/test/compiler/process_callstack.abra b/projects/compiler/test/compiler/process_callstack.abra index ba3f0f0d..9dc12cd5 100644 --- a/projects/compiler/test/compiler/process_callstack.abra +++ b/projects/compiler/test/compiler/process_callstack.abra @@ -26,7 +26,7 @@ val arr = [1].map((i, _) => { /// Expect: at baz (%TEST_DIR%/compiler/process_callstack.abra:10) /// Expect: at bar (%TEST_DIR%/compiler/process_callstack.abra:5) /// Expect: at foo (%TEST_DIR%/compiler/process_callstack.abra:19) -/// Expect: at (%STD_DIR%/prelude.abra:596) +/// Expect: at (%STD_DIR%/prelude.abra:601) /// Expect: at Array.map (%TEST_DIR%/compiler/process_callstack.abra:18) type OneTwoThreeIterator { diff --git a/projects/compiler/test/lexer/chars.abra b/projects/compiler/test/lexer/chars.abra new file mode 100644 index 00000000..5deb6be7 --- /dev/null +++ b/projects/compiler/test/lexer/chars.abra @@ -0,0 +1,4 @@ +'a' +' ' +'{' +'Z' \ No newline at end of file diff --git a/projects/compiler/test/lexer/chars.out.json b/projects/compiler/test/lexer/chars.out.json new file mode 100644 index 00000000..511fb3dc --- /dev/null +++ b/projects/compiler/test/lexer/chars.out.json @@ -0,0 +1,30 @@ +[ + { + "position": [1, 1], + "kind": { + "name": "Char", + "value": "0x61" + } + }, + { + "position": [2, 1], + "kind": { + "name": "Char", + "value": "0x20" + } + }, + { + "position": [3, 1], + "kind": { + "name": "Char", + "value": "0x7b" + } + }, + { + "position": [4, 1], + "kind": { + "name": "Char", + "value": "0x5a" + } + } +] diff --git a/projects/compiler/test/lexer/chars_error_empty.abra b/projects/compiler/test/lexer/chars_error_empty.abra new file mode 100644 index 00000000..94230909 --- /dev/null +++ b/projects/compiler/test/lexer/chars_error_empty.abra @@ -0,0 +1 @@ +'' \ No newline at end of file diff --git a/projects/compiler/test/lexer/chars_error_empty.out b/projects/compiler/test/lexer/chars_error_empty.out new file mode 100644 index 00000000..7d633ea6 --- /dev/null +++ b/projects/compiler/test/lexer/chars_error_empty.out @@ -0,0 +1,4 @@ +Error at %FILE_NAME%:1:2 +Unexpected character ''': + | '' + ^ \ No newline at end of file diff --git a/projects/compiler/test/lexer/chars_error_too_big.abra b/projects/compiler/test/lexer/chars_error_too_big.abra new file mode 100644 index 00000000..30bf9a65 --- /dev/null +++ b/projects/compiler/test/lexer/chars_error_too_big.abra @@ -0,0 +1 @@ +'ab' \ No newline at end of file diff --git a/projects/compiler/test/lexer/chars_error_too_big.out b/projects/compiler/test/lexer/chars_error_too_big.out new file mode 100644 index 00000000..8f5bd87d --- /dev/null +++ b/projects/compiler/test/lexer/chars_error_too_big.out @@ -0,0 +1,4 @@ +Error at %FILE_NAME%:1:3 +Unexpected character 'b': + | 'ab' + ^ \ No newline at end of file diff --git a/projects/compiler/test/parser/literals.abra b/projects/compiler/test/parser/literals.abra index b76daef4..d685402c 100644 --- a/projects/compiler/test/parser/literals.abra +++ b/projects/compiler/test/parser/literals.abra @@ -2,3 +2,4 @@ 1.23 0.01230 true false "hello world 1234" +'a' 'Z' '[' diff --git a/projects/compiler/test/parser/literals.out.json b/projects/compiler/test/parser/literals.out.json index cf136d44..e475002e 100644 --- a/projects/compiler/test/parser/literals.out.json +++ b/projects/compiler/test/parser/literals.out.json @@ -98,6 +98,48 @@ "type": "string", "value": "hello world 1234" } + }, + { + "token": { + "position": [5, 1], + "kind": { + "name": "Char", + "value": "0x61" + } + }, + "kind": { + "name": "literal", + "type": "char", + "value": "0x61" + } + }, + { + "token": { + "position": [5, 5], + "kind": { + "name": "Char", + "value": "0x5a" + } + }, + "kind": { + "name": "literal", + "type": "char", + "value": "0x5a" + } + }, + { + "token": { + "position": [5, 9], + "kind": { + "name": "Char", + "value": "0x5b" + } + }, + "kind": { + "name": "literal", + "type": "char", + "value": "0x5b" + } } ] } diff --git a/projects/compiler/test/run-tests.js b/projects/compiler/test/run-tests.js index fb2139cb..5b3a122c 100644 --- a/projects/compiler/test/run-tests.js +++ b/projects/compiler/test/run-tests.js @@ -22,6 +22,10 @@ const LEXER_TESTS = [ { test: "lexer/strings_interpolation.1.abra", assertions: "lexer/strings_interpolation.1.out.json" }, { test: "lexer/strings_interpolation.2.abra", assertions: "lexer/strings_interpolation.2.out.json" }, { test: "lexer/strings_interpolation_error_unclosed_brace.abra", assertions: "lexer/strings_interpolation_error_unclosed_brace.out" }, + // Chars + { test: "lexer/chars.abra", assertions: "lexer/chars.out.json" }, + { test: "lexer/chars_error_empty.abra", assertions: "lexer/chars_error_empty.out" }, + { test: "lexer/chars_error_too_big.abra", assertions: "lexer/chars_error_too_big.out" }, // Keywords { test: "lexer/keywords.abra", assertions: "lexer/keywords.out.json" }, // Symbols @@ -777,6 +781,7 @@ const COMPILER_TESTS = [ { test: "compiler/ints.abra" }, { test: "compiler/floats.abra" }, { test: "compiler/bools.abra" }, + { test: "compiler/chars.abra" }, { test: "compiler/strings.abra" }, { test: "compiler/arrays.abra" }, { test: "compiler/functions.abra" }, diff --git a/projects/compiler/test/typechecker/literals/literals.abra b/projects/compiler/test/typechecker/literals/literals.abra index b184ead8..c2e5b3dc 100644 --- a/projects/compiler/test/typechecker/literals/literals.abra +++ b/projects/compiler/test/typechecker/literals/literals.abra @@ -1,4 +1,5 @@ 123 0 1.23 0.01 true false -"abcd" \ No newline at end of file +"abcd" +'a' 'Z' \ No newline at end of file diff --git a/projects/compiler/test/typechecker/literals/literals.out.json b/projects/compiler/test/typechecker/literals/literals.out.json index 9fca0d27..e48e576b 100644 --- a/projects/compiler/test/typechecker/literals/literals.out.json +++ b/projects/compiler/test/typechecker/literals/literals.out.json @@ -120,6 +120,36 @@ "kind": "literal", "value": "abcd" } + }, + { + "token": { + "position": [5, 1], + "kind": { + "name": "Char", + "value": "0x61" + } + }, + "type": { + }, + "node": { + "kind": "literal", + "value": "0x61" + } + }, + { + "token": { + "position": [5, 5], + "kind": { + "name": "Char", + "value": "0x5a" + } + }, + "type": { + }, + "node": { + "kind": "literal", + "value": "0x5a" + } } ] } diff --git a/projects/std/src/_intrinsics.abra b/projects/std/src/_intrinsics.abra index f8e34591..1305fd67 100644 --- a/projects/std/src/_intrinsics.abra +++ b/projects/std/src/_intrinsics.abra @@ -19,6 +19,9 @@ export func functionNames(): Pointer @Intrinsic("u64_to_string") export func u64ToString(i: Int): String +// @Intrinsic("char_as_int") +// export func charAsInt(c: Char): Int + @Intrinsic("int_as_float") export func intAsFloat(i: Int): Float diff --git a/projects/std/src/prelude.abra b/projects/std/src/prelude.abra index f95d5cf8..b7fd339a 100644 --- a/projects/std/src/prelude.abra +++ b/projects/std/src/prelude.abra @@ -151,6 +151,11 @@ type Bool { // No methods for Bool } +type Char { + // No methods for Char (yet) + // func asInt(self): Int = intrinsics.charAsInt(self) +} + type String { length: Int _buffer: Pointer