diff --git a/spec/compiler/codegen/c_enum_spec.cr b/spec/compiler/codegen/c_enum_spec.cr index d93ce9a8f76e9caa8b19373a422f366dc1b7ae54..0dd82a4b4b5c8f1d94b3192333d268060c323dbc 100644 --- a/spec/compiler/codegen/c_enum_spec.cr +++ b/spec/compiler/codegen/c_enum_spec.cr @@ -1,23 +1,23 @@ #!/usr/bin/env bin/crystal --run require "../../spec_helper" -CodeGenEnumString = "lib Foo; enum Bar; X, Y, Z = 10, W; end end" +CodeGenCEnumString = "lib Foo; enum Bar; X, Y, Z = 10, W; end end" -describe "Code gen: enum" do +describe "Code gen: c enum" do it "codegens enum value" do - run("#{CodeGenEnumString}; Foo::Bar::X").to_i.should eq(0) + run("#{CodeGenCEnumString}; Foo::Bar::X").to_i.should eq(0) end it "codegens enum value 2" do - run("#{CodeGenEnumString}; Foo::Bar::Y").to_i.should eq(1) + run("#{CodeGenCEnumString}; Foo::Bar::Y").to_i.should eq(1) end it "codegens enum value 3" do - run("#{CodeGenEnumString}; Foo::Bar::Z").to_i.should eq(10) + run("#{CodeGenCEnumString}; Foo::Bar::Z").to_i.should eq(10) end it "codegens enum value 4" do - run("#{CodeGenEnumString}; Foo::Bar::W").to_i.should eq(11) + run("#{CodeGenCEnumString}; Foo::Bar::W").to_i.should eq(11) end [ diff --git a/spec/compiler/codegen/enum_spec.cr b/spec/compiler/codegen/enum_spec.cr new file mode 100644 index 0000000000000000000000000000000000000000..f8764e14cf5d39d837972e0a598e19f594ec4348 --- /dev/null +++ b/spec/compiler/codegen/enum_spec.cr @@ -0,0 +1,130 @@ +require "../../spec_helper" + +describe "Code gen: enum" do + it "codegens enum" do + run(%( + enum Foo + A = 1 + end + + Foo::A + )).to_i.should eq(1) + end + + it "codegens enum without explicit value" do + run(%( + enum Foo + A + B + C + end + + Foo::C + )).to_i.should eq(2) + end + + it "codegens enum value" do + run(%( + enum Foo + A = 1 + end + + Foo::A.value + )).to_i.should eq(1) + end + + it "creates enum from value" do + run(%( + enum Foo + A + B + end + + Foo.new(1).value + )).to_i.should eq(1) + end + + it "codegens enum bitflags (1)" do + run(%( + @[Flags] + enum Foo + A + end + + Foo::A + )).to_i.should eq(1) + end + + it "codegens enum bitflags (2)" do + run(%( + @[Flags] + enum Foo + A + B + end + + Foo::B + )).to_i.should eq(2) + end + + it "codegens enum bitflags (4)" do + run(%( + @[Flags] + enum Foo + A + B + C + end + + Foo::C + )).to_i.should eq(4) + end + + it "codegens enum bitflags None" do + run(%( + @[Flags] + enum Foo + A + end + + Foo::None + )).to_i.should eq(0) + end + + it "codegens enum bitflags All" do + run(%( + @[Flags] + enum Foo + A + B + C + end + + Foo::All + )).to_i.should eq(1 + 2 + 4) + end + + it "codegens enum None redefined" do + run(%( + @[Flags] + enum Foo + A + None = 10 + end + + Foo::None + )).to_i.should eq(10) + end + + it "codegens enum All redefined" do + run(%( + @[Flags] + enum Foo + A + All = 10 + end + + Foo::All + )).to_i.should eq(10) + end +end diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 61c603fe81661db60b8a9b963db3bb20dea109e9..afdd1c5ac3806bc6b3314abde750722b14ee3657 100755 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -553,8 +553,8 @@ describe "Parser" do it_parses "lib C; struct Foo; x : Int**; end end", LibDef.new("C", [StructDef.new("Foo", [Arg.new("x", restriction: "Int".path.pointer_of.pointer_of)])] of ASTNode) it_parses "lib C; struct Foo; x, y, z : Int; end end", LibDef.new("C", [StructDef.new("Foo", [Arg.new("x", restriction: "Int".path), Arg.new("y", restriction: "Int".path), Arg.new("z", restriction: "Int".path)])] of ASTNode) it_parses "lib C; union Foo; end end", LibDef.new("C", [UnionDef.new("Foo")] of ASTNode) - it_parses "lib C; enum Foo; A\nB, C\nD = 1; end end", LibDef.new("C", [EnumDef.new("Foo", [Arg.new("A"), Arg.new("B"), Arg.new("C"), Arg.new("D", 1.int32)])] of ASTNode) - it_parses "lib C; enum Foo; A = 1, B; end end", LibDef.new("C", [EnumDef.new("Foo", [Arg.new("A", 1.int32), Arg.new("B")])] of ASTNode) + it_parses "lib C; enum Foo; A\nB, C\nD = 1; end end", LibDef.new("C", [EnumDef.new("Foo", [Arg.new("A"), Arg.new("B"), Arg.new("C"), Arg.new("D", 1.int32)] of ASTNode)] of ASTNode) + it_parses "lib C; enum Foo; A = 1, B; end end", LibDef.new("C", [EnumDef.new("Foo", [Arg.new("A", 1.int32), Arg.new("B")] of ASTNode)] of ASTNode) it_parses "lib C; enum Foo < UInt16; end end", LibDef.new("C", [EnumDef.new("Foo", base_type: "UInt16".path)] of ASTNode) it_parses "lib C; Foo = 1; end", LibDef.new("C", [Assign.new("Foo".path, 1.int32)] of ASTNode) it_parses "lib C\nfun getch = GetChar\nend", LibDef.new("C", [FunDef.new("getch", real_name: "GetChar")] of ASTNode) @@ -869,6 +869,15 @@ describe "Parser" do it_parses %("hello \#{1}" \\\n "\#{2} world"), StringInterpolation.new(["hello ".string, 1.int32, 2.int32, " world".string] of ASTNode) assert_syntax_error %("foo" "bar") + it_parses "enum Foo; A\nB, C\nD = 1; end", EnumDef.new("Foo", [Arg.new("A"), Arg.new("B"), Arg.new("C"), Arg.new("D", 1.int32)] of ASTNode) + it_parses "enum Foo; A = 1, B; end", EnumDef.new("Foo", [Arg.new("A", 1.int32), Arg.new("B")] of ASTNode) + it_parses "enum Foo < UInt16; end", EnumDef.new("Foo", base_type: "UInt16".path) + it_parses "enum Foo : UInt16; end", EnumDef.new("Foo", base_type: "UInt16".path) + it_parses "enum Foo; def foo; 1; end; end", EnumDef.new("Foo", [Def.new("foo", body: 1.int32)] of ASTNode) + it_parses "enum Foo; A = 1\ndef foo; 1; end; end", EnumDef.new("Foo", [Arg.new("A", 1.int32), Def.new("foo", body: 1.int32)] of ASTNode) + it_parses "enum Foo; A = 1\ndef foo; 1; end\ndef bar; 2; end\nend", EnumDef.new("Foo", [Arg.new("A", 1.int32), Def.new("foo", body: 1.int32), Def.new("bar", body: 2.int32)] of ASTNode) + it_parses "enum Foo; A = 1\ndef self.foo; 1; end\nend", EnumDef.new("Foo", [Arg.new("A", 1.int32), Def.new("foo", receiver: "self".var, body: 1.int32)] of ASTNode) + %w(def macro class struct module fun alias abstract include extend lib).each do |keyword| assert_syntax_error "def foo\n#{keyword}\nend", Def.new("foo", body: keyword.call) end diff --git a/spec/compiler/type_inference/c_enum_spec.cr b/spec/compiler/type_inference/c_enum_spec.cr index d8a12b587968aef52386decd5ea6ef46eae23cda..4c08a560c0ff1f759943b22e8f88f13095840deb 100755 --- a/spec/compiler/type_inference/c_enum_spec.cr +++ b/spec/compiler/type_inference/c_enum_spec.cr @@ -1,7 +1,7 @@ #!/usr/bin/env bin/crystal --run require "../../spec_helper" -describe "Type inference: enum" do +describe "Type inference: c enum" do it "types enum value" do assert_type("lib Foo; enum Bar; X, Y, Z = 10, W; end; end; Foo::Bar::X") { int32 } end diff --git a/spec/compiler/type_inference/enum_spec.cr b/spec/compiler/type_inference/enum_spec.cr new file mode 100644 index 0000000000000000000000000000000000000000..3c0b6a79e05b2f7663c08d40f2122153af7c847c --- /dev/null +++ b/spec/compiler/type_inference/enum_spec.cr @@ -0,0 +1,185 @@ +require "../../spec_helper" + +describe "Type inference: enum" do + it "types enum" do + assert_type(%( + enum Foo + A = 1 + end + Foo::A + )) { types["Foo"] } + end + + it "types enum value" do + assert_type(%( + enum Foo + A = 1 + end + Foo::A.value + )) { int32 } + end + + it "disallows implicit conversion of int to enum" do + assert_error %( + enum Foo + A = 1 + end + + def foo(x : Foo) + end + + foo 1 + ), "mno overload matches 'foo' with types Int32" + end + + it "finds method in enum type" do + assert_type(%( + struct Enum + def foo + 1 + end + end + + enum Foo + A = 1 + end + + Foo::A.foo + )) { int32 } + end + + it "finds class method in enum type" do + assert_type(%( + struct Enum + def self.foo + 1 + end + end + + enum Foo + A = 1 + end + + Foo.foo + )) { int32 } + end + + it "errors if using a name twice" do + assert_error %( + enum Foo + A + A + end + ), + "enum 'Foo' already contains a member named 'A'" + end + + it "creates enum from value" do + assert_type(%( + enum Foo + A + B + end + + Foo.new(1) + )) { types["Foo"] } + end + + it "defines method on enum" do + assert_type(%( + enum Foo + A + B + + def foo + 1 + end + end + + Foo::A.foo + )) { int32 } + end + + it "defines class method on enum" do + assert_type(%( + enum Foo + A + B + + def self.foo + 1 + end + end + + Foo.foo + )) { int32 } + end + + it "reopens an enum" do + assert_type(%( + enum Foo + A + B + end + + enum Foo + def foo + 1 + end + end + + Foo::A.foo + )) { int32 } + end + + it "errors if reopen but not enum" do + assert_error %( + class Foo + end + + enum Foo + A + B + end + ), + "Foo is not a enum, it's a class" + end + + it "errors if reopen and tries to define constant" do + assert_error %( + enum Foo + A + B + end + + enum Foo + C + end + ), + "can't reopen enum and add more constants to it" + end + + it "has None value when defined as @[Flags]" do + assert_type(%( + @[Flags] + enum Foo + A + B + end + + Foo::None.value + )) { int32 } + end + + it "has All value when defined as @[Flags]" do + assert_type(%( + @[Flags] + enum Foo + A + B + end + + Foo::All.value + )) { int32 } + end +end diff --git a/src/compiler/crystal/codegen/cast.cr b/src/compiler/crystal/codegen/cast.cr index be822d6ab06eb49389a83baf828e9f4d0313ff0d..a9d004275868476256a0f7a15f0d96f29dbcad44 100644 --- a/src/compiler/crystal/codegen/cast.cr +++ b/src/compiler/crystal/codegen/cast.cr @@ -324,7 +324,7 @@ class Crystal::CodeGenVisitor < Crystal::Visitor union_ptr end - def upcast_distinct(value, to_type : CEnumType, from_type : Type) + def upcast_distinct(value, to_type : EnumType, from_type : Type) value end diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 85e257923f4d8eaba79e2f1a21bf2730595e530d..032b7b0a549bb8372f1bbbc3b9e7d1b740372d57 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -478,7 +478,7 @@ module Crystal end def visit(node : EnumDef) - node.c_enum_type.types.each_value do |type| + node.enum_type.try &.types.each_value do |type| declare_const(type as Const) end @last = llvm_nil diff --git a/src/compiler/crystal/codegen/llvm_typer.cr b/src/compiler/crystal/codegen/llvm_typer.cr index 524e6a6d66c5bc9fccf80a229ecf42c3f658cb28..1b20fe00b9a0d013dc1d14545be4f05606d1e44d 100644 --- a/src/compiler/crystal/codegen/llvm_typer.cr +++ b/src/compiler/crystal/codegen/llvm_typer.cr @@ -65,7 +65,7 @@ module Crystal LLVM::Int32 end - def create_llvm_type(type : CEnumType) + def create_llvm_type(type : EnumType) llvm_type(type.base_type) end diff --git a/src/compiler/crystal/codegen/primitives.cr b/src/compiler/crystal/codegen/primitives.cr index 551c583eedb3f9b39c83ea0053c493cdb29411a5..639923a3c111fa5717f94537a25bc4c9f9ba8439 100644 --- a/src/compiler/crystal/codegen/primitives.cr +++ b/src/compiler/crystal/codegen/primitives.cr @@ -69,6 +69,8 @@ class Crystal::CodeGenVisitor < Crystal::Visitor codegen_primitive_pointer_diff node, target_def, call_args when :tuple_indexer_known_index codegen_primitive_tuple_indexer_known_index node, target_def, call_args + when :enum_value, :enum_new + call_args[0] else raise "Bug: unhandled primitive in codegen: #{node.name}" end diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 4ae88256217ad2878c172913923721873b13d0c0..4bf7935f05632cfe0c4ee0f58fd4330afded8f92 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -260,7 +260,7 @@ module Crystal if @stats time = Time.now value = yield - puts "#{label}: #{Time.now - time} seconds" + puts "#{label}: #{Time.now - time}" value else yield diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index 4dca6b09e95e9b38995056ba75ab24a0071881ee..978cb0b641b245dc1dfaf11c4eb5f490a099a826 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -603,13 +603,28 @@ module Crystal when "@instance_vars" return @last = MacroType.instance_vars(@scope) when "@length" - if (scope = @scope).is_a?(TupleInstanceType) + scope = @scope.try &.instance_type + if scope.is_a?(TupleInstanceType) return @last = NumberLiteral.new(scope.tuple_types.length) end when "@superclass" return @last = MacroType.superclass(@scope) when "@type" return @last = MacroType.new(@scope) + when "@enum_members" + scope = @scope.try &.instance_type + if scope.is_a?(EnumType) + names = Array(ASTNode).new(scope.types.length) + scope.types.each_key do |name| + names << MacroId.new(name) + end + return @last = ArrayLiteral.new names + end + when "@enum_flags" + scope = @scope.try &.instance_type + if scope.is_a?(EnumType) + return @last = BoolLiteral.new(scope.flags?) + end end node.raise "unknown macro instance var: '#{node.name}'" diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 670bb5ea8009701e8a0b29b1f93ab24ff71fd2bf..70477a8a141e8b1d772d4e2703bfbd25b14f38f0 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -89,6 +89,10 @@ module Crystal @struct.abstract = true @struct.struct = true + @types["Enum"] = @enum = NonGenericClassType.new self, self, "Enum", @value + @enum.abstract = true + @enum.struct = true + @types["Function"] = @function = FunType.new self, self, "Function", @value, ["T"] @function.variadic = true @@ -297,6 +301,7 @@ module Crystal getter :exception getter :tuple getter :function + getter :enum def class_type @class diff --git a/src/compiler/crystal/semantic/after_type_inference_transformer.cr b/src/compiler/crystal/semantic/after_type_inference_transformer.cr index 363af9d1f4c7b6b1e8191a058e81fcf3be08206c..0668300d4e5760c5fe7778bc3f746c8cc074ec92 100644 --- a/src/compiler/crystal/semantic/after_type_inference_transformer.cr +++ b/src/compiler/crystal/semantic/after_type_inference_transformer.cr @@ -169,7 +169,7 @@ module Crystal def transform(node : EnumDef) super - node.c_enum_type.types.each_value do |const| + node.enum_type.try &.types.each_value do |const| (const as Const).initialized = true end diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index 62c980a95ac156d9e75b1febb8d3e692a2aa3e95..6c57f9abbe66a37a25532419e56b7405de30ca3c 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -471,7 +471,7 @@ module Crystal end class EnumDef - property! c_enum_type + property enum_type end class Yield diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 052a1a99c1c3681758105ced0840b96f9fee86a0..d38e46baf9627c147f745f2bd7d72afdaa782b4c 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -251,8 +251,12 @@ module Crystal end class IntegerType - def restrict(other : CEnumType, context) - self == other.base_type ? self : nil + def restrict(other : EnumType, context) + if other.c_enum? + self == other.base_type ? self : nil + else + super + end end end diff --git a/src/compiler/crystal/semantic/type_inference.cr b/src/compiler/crystal/semantic/type_inference.cr index 83be1d8a8c608447015cd91fb73657ac6550605a..621d81914a2f3d3d555f881fa464e78bb2116447 100644 --- a/src/compiler/crystal/semantic/type_inference.cr +++ b/src/compiler/crystal/semantic/type_inference.cr @@ -21,6 +21,7 @@ module Crystal ValidStructDefAttributes = %w(Packed) ValidDefAttributes = %w(AlwaysInline NoInline Raises ReturnsTwice) ValidFunDefAttributes = %w(AlwaysInline NoInline Raises ReturnsTwice) + ValidEnumDefAttributes = %w(Flags) getter mod property! scope @@ -1681,36 +1682,78 @@ module Crystal end def visit(node : EnumDef) - type = current_type.types[node.name]? - if type - node.raise "#{node.name} is already defined" - else - if base_type = node.base_type - base_type.accept self - enum_base_type = base_type.type.instance_type - unless enum_base_type.is_a?(IntegerType) - base_type.raise "enum base type must be an integer type" - end - else - enum_base_type = @mod.int32 + check_valid_attributes node, ValidEnumDefAttributes, "enum" + + enum_type = current_type.types[node.name]? + if enum_type + unless enum_type.is_a?(EnumType) + node.raise "#{node.name} is not a enum, it's a #{enum_type.type_desc}" end + end - enum_type = CEnumType.new(@mod, current_type, node.name, enum_base_type) + if base_type = node.base_type + base_type.accept self + enum_base_type = base_type.type.instance_type + unless enum_base_type.is_a?(IntegerType) + base_type.raise "enum base type must be an integer type" + end + else + enum_base_type = @mod.int32 + end + + is_lib = current_type.is_a?(LibType) + is_flags = node.has_attribute?("Flags") + all_value = 0_u64 + existed = !!enum_type + enum_type ||= EnumType.new(@mod, current_type, node.name, enum_base_type, is_lib, is_flags) + + pushing_type(enum_type) do + counter = is_flags ? 1 : 0 + node.members.each do |member| + case member + when Arg + if existed + member.raise "can't reopen enum and add more constants to it" + end - pushing_type(enum_type) do - counter = 0 - node.constants.each do |constant| - if default_value = constant.default_value + if default_value = member.default_value counter = interpret_enum_value(default_value) end - constant.default_value = NumberLiteral.new(counter, enum_base_type.kind) - enum_type.add_constant constant - counter += 1 + all_value |= counter + const_value = NumberLiteral.new(counter, enum_base_type.kind) + member.default_value = const_value + if enum_type.types.has_key?(member.name) + member.raise "enum '#{enum_type}' already contains a member named '#{member.name}'" + end + enum_type.add_constant member + const_value.type = enum_type unless is_lib + counter = is_flags ? counter * 2 : counter + 1 + when Def + member.accept self end end + end + + unless existed + if is_flags + unless enum_type.types["None"]? + none = NumberLiteral.new(0, enum_base_type.kind) + none.type = enum_type unless is_lib + enum_type.add_constant Arg.new("None", default_value: none) + end - node.c_enum_type = current_type.types[node.name] = enum_type + unless enum_type.types["All"]? + all = NumberLiteral.new(all_value, enum_base_type.kind) + all.type = enum_type unless is_lib + enum_type.add_constant Arg.new("All", default_value: all) + end + end + + node.enum_type = current_type.types[node.name] = enum_type end + + node.type = mod.nil + false end @@ -2153,6 +2196,10 @@ module Crystal node.type = mod.int64 when :class_name node.type = mod.string + when :enum_value + # Nothing to do + when :enum_new + # Nothing to do else node.raise "Bug: unhandled primitive in type inference: #{node.name}" end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 2980e2df8bb7f384684301446d7feadddf530a7f..3100fe2bc8a91023d6d43c7317532e48f9cfeffc 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -22,10 +22,6 @@ module Crystal Attribute.any?(attributes, name) end - def accepts_attributes? - false - end - def name_column_number @location.try(&.column_number) || 0 end @@ -673,10 +669,6 @@ module Crystal def initialize(@name) end - def accepts_attributes? - true - end - def name_length name.length end @@ -835,10 +827,6 @@ module Crystal @name_column_number = 0 end - def accepts_attributes? - true - end - def accept_children(visitor) @receiver.try &.accept visitor @args.each &.accept visitor @@ -1155,10 +1143,6 @@ module Crystal @body = Expressions.from body end - def accepts_attributes? - true - end - def accept_children(visitor) @superclass.try &.accept visitor @body.accept visitor @@ -1561,10 +1545,6 @@ module Crystal def initialize(@name, @args = [] of Arg, @return_type = nil, @varargs = false, @body = nil, @real_name = name) end - def accepts_attributes? - true - end - def accept_children(visitor) @args.each &.accept visitor @return_type.try &.accept visitor @@ -1614,10 +1594,6 @@ module Crystal class StructDef < StructOrUnionDef property :attributes - def accepts_attributes? - true - end - def clone_without_location StructDef.new(@name, @fields.clone) end @@ -1631,22 +1607,23 @@ module Crystal class EnumDef < ASTNode property :name - property :constants + property :members property :base_type + property :attributes - def initialize(@name, @constants = [] of Arg, @base_type = nil) + def initialize(@name, @members = [] of ASTNode, @base_type = nil) end def accept_children(visitor) - @constants.each &.accept visitor + @members.each &.accept visitor @base_type.try &.accept visitor end def clone_without_location - EnumDef.new(@name, @constants.clone, @base_type.clone) + EnumDef.new(@name, @members.clone, @base_type.clone) end - def_equals_and_hash @name, @constants, @base_type + def_equals_and_hash @name, @members, @base_type end class ExternalVar < ASTNode @@ -1658,10 +1635,6 @@ module Crystal def initialize(@name, @type_spec, @real_name = nil) end - def accepts_attributes? - true - end - def accept_children(visitor) @type_spec.accept visitor end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index f25b1ac5699458f90fedc97d8d78377b9fdce6bc..f8ece985290db6523b791d5fa6e6c360e080a16b 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -858,6 +858,10 @@ module Crystal check_not_inside_def("can't define module inside def") do parse_module_def end + when :enum + check_not_inside_def("can't define enum inside def") do + parse_enum_def + end when :while parse_while when :until @@ -3513,7 +3517,7 @@ module Crystal when :union parse_struct_or_union UnionDef when :enum - parse_enum + parse_enum_def when :ifdef parse_ifdef check_end: true, inside_lib: true else @@ -3780,14 +3784,15 @@ module Crystal fields end - def parse_enum + def parse_enum_def next_token_skip_space_or_newline check :CONST name = @token.value.to_s next_token_skip_space - if @token.type == :"<" + case @token.type + when :"<", :":" next_token_skip_space_or_newline base_type = parse_single_type skip_statement_end @@ -3795,33 +3800,45 @@ module Crystal next_token_skip_statement_end end - constants = [] of Arg - while !@token.keyword?(:end) - check :CONST + members = [] of ASTNode + until @token.keyword?(:end) + case @token.type + when :CONST + constant_name = @token.value.to_s + next_token_skip_space + if @token.type == :"=" + next_token_skip_space_or_newline - constant_name = @token.value.to_s - next_token_skip_space - if @token.type == :"=" - next_token_skip_space_or_newline + constant_value = parse_logical_or + next_token_skip_statement_end + else + constant_value = nil + skip_statement_end + end - constant_value = parse_logical_or - next_token_skip_statement_end - else - constant_value = nil - skip_statement_end - end + case @token.type + when :",", :";" + next_token_skip_statement_end + end - if @token.type == :"," - next_token_skip_statement_end + members << Arg.new(constant_name, constant_value) + when :IDENT + if @token.value == :def + members << parse_def + else + unexpected_token + end + when :";", :NEWLINE + skip_statement_end + else + unexpected_token end - - constants << Arg.new(constant_name, constant_value) end check_ident :end next_token_skip_space - EnumDef.new name, constants, base_type + EnumDef.new name, members, base_type end def node_and_next_token(node) diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index d22465763cb5a330de567721ad86de882c09887a..a15f639fd4c427263a25d8d3184547b2cbf0ac40 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -1015,9 +1015,9 @@ module Crystal end @str << newline with_indent do - node.constants.each do |constant| + node.members.each do |member| append_indent - constant.accept self + member.accept self @str << newline end end diff --git a/src/compiler/crystal/syntax/transformer.cr b/src/compiler/crystal/syntax/transformer.cr index b54a370f82bd8cebb126ed8ebc41bb2daff9ea97..4ba288c60ba961d8d2c31089deecdcbf442bf45e 100644 --- a/src/compiler/crystal/syntax/transformer.cr +++ b/src/compiler/crystal/syntax/transformer.cr @@ -465,6 +465,7 @@ module Crystal end def transform(node : EnumDef) + transform_many node.members node end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 807d106edc4bc75b022ae97ad129444a4e93582d..880ea6bc27bf22c1f1fde24d0f6fb5f5ebf0930b 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -1303,6 +1303,8 @@ module Crystal def virtual_type if leaf? && !self.abstract self + elsif struct? + self else virtual_type! end @@ -2220,11 +2222,22 @@ module Crystal end end - class CEnumType < NamedType + class EnumType < NamedType + include DefContainer + include DefInstanceContainer + getter base_type + getter? flags - def initialize(program, container, name, @base_type) + def initialize(program, container, name, @base_type, @c_enum, @flags) super(program, container, name) + + add_def Def.new("value", [] of Arg, Primitive.new(:enum_value, @base_type)) + metaclass.add_def Def.new("new", [Arg.new("value", type: @base_type)], Primitive.new(:enum_new, self)) + end + + def parents + @parents ||= [program.enum] of Type end def add_constant(constant) @@ -2232,26 +2245,16 @@ module Crystal end def c_enum? - true + @c_enum end def primitive_like? - true - end - - def parents - nil + c_enum? end def type_desc "enum" end - - def to_s(io) - container.to_s(io) - io << "::" - name.to_s(io) - end end class MetaclassType < ClassType @@ -2267,6 +2270,8 @@ module Crystal @instance_type = instance_type super_class ||= if instance_type.is_a?(ClassType) && instance_type.superclass instance_type.superclass.not_nil!.metaclass as ClassType + elsif instance_type.is_a?(EnumType) + @program.enum.metaclass as MetaclassType else @program.class_type end @@ -2552,7 +2557,7 @@ module Crystal property value getter scope_types getter scope - property! vars + property vars property used property? visited property? initialized diff --git a/src/enum.cr b/src/enum.cr new file mode 100644 index 0000000000000000000000000000000000000000..fe1dad436556a52d580b0e10e16c72a26181cd76 --- /dev/null +++ b/src/enum.cr @@ -0,0 +1,119 @@ +struct Enum + include Comparable(self) + + macro def to_s(io : IO) : Nil + {% if @enum_flags %} + if value == 0 + io << "None" + else + found = false + {% for member in @enum_members %} + {% if member.stringify != "All" %} + if {{member}}.value != 0 && (value & {{member}}.value) == {{member}}.value + io << ", " if found + io << {{member.stringify}} + found = true + end + {% end %} + {% end %} + io << value unless found + end + {% else %} + io << to_s + {% end %} + nil + end + + macro def to_s : String + {% if @enum_flags %} + String.build { |io| to_s(io) } + {% else %} + case value + {% for member in @enum_members %} + when {{member}}.value + {{member.stringify}} + {% end %} + else + value.to_s + end + {% end %} + end + + def +(other : Int) + self.class.new(value + other) + end + + def -(other : Int) + self.class.new(value - other) + end + + def |(other : self) + self.class.new(value | other.value) + end + + def &(other : self) + self.class.new(value & other.value) + end + + def ^(other : self) + self.class.new(value ^ other.value) + end + + def ~(other : self) + self.class.new(value ~ other.value) + end + + def <=>(other : self) + value <=> other.value + end + + def includes?(other : self) + (value & other.value) != 0 + end + + def ==(other : self) + value == other.value + end + + macro def self.names : Array(String) + {{ @enum_members.map &.stringify }} + end + + # macro def self.values : Array(self) + # {{ @enum_members }} + # end + + # macro def self.to_h : Hash(String, self) + # { + # {% for member in @enum_members %} + # {{member.stringify}} => {{member}}, + # {% end %} + # } + # end + + # def self.parse(string) + # value = parse?(string) + # if value + # value + # else + # raise "Uknonwn enum #{self} value: #{string}" + # end + # end + + # macro def self.parse?(string) : self ? + # case string.downcase + # {% for member in @enum_members %} + # when {{member.stringify.downcase}} + # {{member}} + # {% end %} + # else + # nil + # end + # end + + # def self.each + # to_h.each do |key, value| + # yield key, value + # end + # end +end diff --git a/src/prelude.cr b/src/prelude.cr index b2f1f85e7d990ce0dfc0809cfbfc175d7895eb7b..bd71fb7352bc9da8d200e96b225fbca3411112ef 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -26,6 +26,7 @@ require "range" require "char_reader" require "string" require "symbol" +require "enum" require "static_array" require "array" require "hash"