Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lexer for Zig lang #1533

Merged
merged 9 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/rouge/demos/zig
@@ -0,0 +1,6 @@
const std = @import("std");
const warn = std.debug.warn;

fn add_floats(x: f16, y: f16) f16 {
return x + y;
}
139 changes: 139 additions & 0 deletions lib/rouge/lexers/zig.rb
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class Zig < RegexLexer
tag 'zig'
aliases 'zir'
filenames '*.zig'
mimetypes 'text/x-zig'

title 'Zig'
desc 'The Zig programming language (ziglang.org)'

def self.keywords
@keywords ||= %w(
align linksection threadlocal struct enum union error break return
anyframe fn c_longlong c_ulonglong c_longdouble c_void comptime_float
c_short c_ushort c_int c_uint c_long c_ulong continue asm defer
errdefer const var extern packed export pub if else switch and or
orelse while for bool unreachable try catch async suspend nosuspend
await resume undefined usingnamespace test void noreturn type
anyerror usize noalias inline noinline comptime callconv volatile
allowzero
)
end

def self.builtins
@builtins ||= %w(
@addWithOverflow @as @atomicLoad @atomicStore @bitCast @breakpoint
@alignCast @alignOf @cDefine @cImport @cInclude @bitOffsetOf
@atomicRmw @bytesToSlice @byteOffsetOf @OpaqueType @panic @ptrCast
@bitReverse @Vector @sin @cUndef @canImplicitCast @clz @cmpxchgWeak
@cmpxchgStrong @compileError @compileLog @ctz @popCount @divExact
@divFloor @cos @divTrunc @embedFile @export @tagName @TagType
@errorName @call @errorReturnTrace @fence @fieldParentPtr @field
@unionInit @errorToInt @intToEnum @enumToInt @setAlignStack @frame
@Frame @exp @exp2 @log @log2 @log10 @fabs @floor @ceil @trunc @round
@floatCast @intToFloat @floatToInt @boolToInt @errSetCast @intToError
@frameAddress @import @newStackCall @asyncCall @intToPtr @intCast
@frameSize @memcpy @memset @mod @mulWithOverflow @splat @ptrToInt
@rem @returnAddress @setCold @Type @shuffle @setGlobalLinkage
@setGlobalSection @shlExact @This @hasDecl @hasField
@setRuntimeSafety @setEvalBranchQuota @setFloatMode @shlWithOverflow
@shrExact @sizeOf @bitSizeOf @sqrt @byteSwap @subWithOverflow
@sliceToBytes comptime_int @truncate @typeInfo @typeName @TypeOf
)
end

id = /[a-z_]\w*/i
escapes = /\\ ([nrt'"\\0] | x\h{2} | u\h{4} | U\h{8})/x

state :bol do
mixin :whitespace
rule %r/#\s[^\n]*/, Comment::Special
rule(//) { pop! }
end

state :attribute do
mixin :whitespace
mixin :literals
rule %r/[(,)=:]/, Name::Decorator
rule %r/\]/, Name::Decorator, :pop!
rule id, Name::Decorator
end

state :whitespace do
rule %r/\s+/, Text
rule %r(//[^\n]*), Comment
end

state :root do
rule %r/\n/, Text, :bol

mixin :whitespace

rule %r/\b(?:(i|u)[0-9]+)\b/, Keyword::Type
rule %r/\b(?:f(16|32|64|128))\b/, Keyword::Type
rule %r/\b(?:(isize|usize))\b/, Keyword::Type

mixin :literals

rule %r/'#{id}/, Name::Variable
rule %r/([.]?)(\s*)(@?#{id})(\s*)([(]?)/ do |m|
name = m[3]
t = if self.class.keywords.include? name
Keyword
elsif self.class.builtins.include? name
Name::Builtin
elsif !m[1].empty? && !m[5].empty?
Name::Function
elsif !m[1].empty?
Name::Property
else
Name
end

groups Punctuation, Text, t, Text, Punctuation
end

rule %r/[()\[\]{}|,:;]/, Punctuation
rule %r/[*\/!@~&+%^<>=\?-]|\.{1,3}/, Operator
end

state :literals do
rule %r/\b(?:true|false|null)\b/, Keyword::Constant
rule %r(
' (?: #{escapes} | [^\\] ) '
)x, Str::Char

rule %r/"/, Str, :string
rule %r/r(#*)".*?"\1/m, Str

dot = /[.][0-9_]+/
exp = /e[-+]?[0-9_]+/

rule %r(
[0-9]+
(#{dot} #{exp}?
|#{dot}? #{exp}
)
)x, Num::Float

rule %r(
( 0b[10_]+
| 0x[0-9a-fA-F_]+
| [0-9_]+
)
)x, Num::Integer
end

state :string do
rule %r/"/, Str, :pop!
rule escapes, Str::Escape
rule %r/[^"\\]+/m, Str
end
end
end
end
57 changes: 57 additions & 0 deletions spec/lexers/zig_spec.rb
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::Zig do
let(:subject) { Rouge::Lexers::Zig.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.zig'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-zig'
end
end

describe 'lexing' do
include Support::Lexing;

it 'classifies identifiers' do
assert_tokens_equal 'var x: i32 = 100;',
['Keyword', 'var'],
['Text', ' '],
['Name', 'x'],
['Punctuation', ':'],
['Text', ' '],
['Keyword.Type', 'i32'],
['Text', ' '],
['Operator', '='],
['Text', ' '],
['Literal.Number.Integer', '100'],
['Punctuation', ';']

assert_tokens_equal 'var x: f32 = 1.01;',
['Keyword', 'var'],
['Text', ' '],
['Name', 'x'],
['Punctuation', ':'],
['Text', ' '],
['Keyword.Type', 'f32'],
['Text', ' '],
['Operator', '='],
['Text', ' '],
['Literal.Number.Float', '1.01'],
['Punctuation', ';']
end

it 'recognizes exponents in integers and reals' do
assert_tokens_equal '1e6', ['Literal.Number.Float', '1e6']
assert_tokens_equal '123_456', ['Literal.Number.Integer', '123_456']
assert_tokens_equal '3.14159_26', ['Literal.Number.Float', '3.14159_26']
assert_tokens_equal '3.141_592e-20', ['Literal.Number.Float', '3.141_592e-20']
end
end
end
141 changes: 141 additions & 0 deletions spec/visual/samples/zig
@@ -0,0 +1,141 @@
// builtin demo

const std = @import("std");
const warn = std.debug.warn;

fn add_integers(x: i78, y: i78) i78 {
var res: i78 = undefined;

if (@addWithOverflow(i78, x, y, &res)) {
warn("overflow detected!\n", .{});
return 0;
}

return res;
}

test "add_integers" {
warn("{}\n", .{add_integers(123, 171781717)});
warn("{}\n", .{add_integers(97239729372893729998991, 99900091788888881781717)});
}

// demo of real numbers

const std = @import("std");
const warn = std.debug.warn;

fn add_floats(x: f128, y: f128) f128 {
return x + y;
}

test "add_floats" {
const x = 1.23;
const y = -.0019;
warn("{} + {} = {}\n", .{x, y, add_floats(x, y)});
}


// unions and enums demo - a simple arithmetic expression evaluator

const std = @import("std");
const warn = std.debug.warn;

/// this expresses the variants that an arithmetic expression can take
const Expr = union(enum) {
Val: i32,
Add: Payload,
Div: Payload,
Sub: Payload,
Mul: Payload,
};

const Payload = struct {
left: *const Expr,
right: *const Expr,
};

fn show_helper(expr: *const Payload, expr_name: []const u8, stdout: *const @TypeOf(std.io.getStdOut().outStream())) anyerror!void {
try stdout.print("{}", .{expr_name});
try show(expr.left, stdout);
try stdout.print(", ", .{});
try show(expr.right, stdout);
try stdout.print(")", .{});
}

fn show(e: *const Expr, stdout: *const @TypeOf(std.io.getStdOut().outStream())) anyerror!void {
switch (e.*) {
Expr.Val => |n| try stdout.print("Val {}", .{n}),
Expr.Add => |a| try show_helper(&a, "Add (", stdout),
Expr.Sub => |s| try show_helper(&s, "Sub (", stdout),
Expr.Mul => |m| try show_helper(&m, "Mul (", stdout),
Expr.Div => |d| try show_helper(&d, "Div (", stdout),
else => unreachable,
}
}

fn eval(e: *const Expr) i32 {
return switch (e.*) {
Expr.Val => |v| v,
Expr.Add => |a| eval(a.left) + eval(a.right),
Expr.Sub => |s| eval(s.left) - eval(s.right),
Expr.Mul => |m| eval(m.left) * eval(m.right),
Expr.Div => |d| return if (eval(d.right) == 0) eval(d.left) else @divTrunc(eval(d.left), eval(d.right)),
else => unreachable,
};
}

pub fn main() !void {
const stdout = std.io.getStdOut().outStream();

const e1 = Expr{ .Val = 100 };
try show(&e1, &stdout);
try stdout.print(" = {}\n", .{eval(&e1)});

const e2 = Expr{ .Div = .{ .left = &Expr{ .Val = 10 }, .right = &Expr{ .Val = 2 } } };
try show(&e2, &stdout);
try stdout.print(" = {}\n", .{eval(&e2)});

const e3 = Expr{
.Div = .{
.left = &Expr{
.Mul = .{
.left = &Expr{ .Val = 5 },
.right = &Expr{ .Val = 4 },
},
},
.right = &Expr{ .Val = 2 },
},
};
try show(&e3, &stdout);
try stdout.print(" = {}\n", .{eval(&e3)});

const e4 = Expr{
.Add = .{
.left = &Expr{
.Mul = .{
.left = &Expr{ .Val = 5 },
.right = &Expr{ .Val = 4 },
},
},
.right = &Expr{
.Sub = .{
.left = &Expr{ .Val = 100 },
.right = &Expr{
.Div = .{
.left = &Expr{ .Val = 12 },
.right = &Expr{ .Val = 4 },
},
},
},
},
},
};

try show(&e4, &stdout);
try stdout.print(" = {}\n", .{eval(&e4)});

const e5 = Expr{ .Div = .{ .left = &Expr{ .Val = 100 }, .right = &Expr{ .Val = 0 } } };
try show(&e5, &stdout);
try stdout.print(" = {}\n", .{eval(&e5)});
}