Skip to content

Commit

Permalink
Add basic Hack support
Browse files Browse the repository at this point in the history
See hacklang.org

Changes to PHP and C++ (and corresponding tests) to make sure that
ambiguities are covered.

Will add XHP support in a separate pull request.

refs rouge-ruby#392
  • Loading branch information
fredemmott committed Aug 10, 2017
1 parent 6275798 commit db7cf94
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 4 deletions.
5 changes: 5 additions & 0 deletions lib/rouge/lexers/cpp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def self.reserved
rule /\s*(?=>)/m, Text, :pop!
mixin :whitespace
end

# high priority for filename matches
def self.analyze_text(*)
0.3
end
end
end
end
52 changes: 52 additions & 0 deletions lib/rouge/lexers/hack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #

module Rouge
module Lexers
load_lexer 'php.rb'

class Hack < PHP
title 'Hack'
desc 'The Hack programming language (hacklang.org)'
tag 'hack'
aliases 'hack', 'hh'
filenames '*.php', '*.hh'

def self.analyze_text(text)
return 1 if /<\?hh/ =~ text
return 0.5 if text.shebang?('hhvm')
return 0.2 if /async function/ =~ text
return 0.2 if /\): Awaitable</ =~ text
return 0.2 if /(vec|dict|keyset)\[/ =~ text
return 0.2 if /(Map|Set|Vec)\s*{/ =~ text
# Never succeed on a filename-only match:
# - PHP wins for .php
# - C++ wins for .hh
-1
end

def self.keywords
@hh_keywords ||= super.merge Set.new %w(
type newtype enum
as super
async await Awaitable
vec dict keyset
void int string bool float double
arraykey num Stringish
)
end

prepend :template do
rule /<\?hh(\s*\/\/\s*(strict|decl|partial))?$/, Comment::Preproc, :php
end

prepend :php do
rule %r((/\*\s*)(HH_(?:IGNORE_ERROR|FIXME)\[\d+\])([^*]*)(\*/)) do
groups Comment::Preproc, Comment::Preproc, Comment::Multiline, Comment::Preproc
end

rule %r(// UNSAFE(?:_EXPR|_BLOCK)?), Comment::Preproc
rule %r(/\*\s*UNSAFE_EXPR\s*\*/), Comment::Preproc
end
end
end
end
6 changes: 3 additions & 3 deletions lib/rouge/lexers/php.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*- #

module Rouge
module Lexers
class PHP < TemplateLexer
module Lexers class PHP < TemplateLexer
title "PHP"
desc "The PHP scripting language (php.net)"
tag 'php'
Expand Down Expand Up @@ -77,7 +76,8 @@ def self.keywords

def self.analyze_text(text)
return 1 if text.shebang?('php')
return 0.3 if /<\?(?!xml)/ =~ text
return 1 if /^<\?php/ =~ text
return 0.3 if /<\?(?!xml|hh)/ =~ text
0
end

Expand Down
4 changes: 3 additions & 1 deletion spec/lexers/cpp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

assert_guess :filename => 'foo.hpp'
assert_guess :filename => 'foo.h++'
assert_guess :filename => 'foo.hh'
assert_guess :filename => 'foo.hxx'

# Disambiguate with hacklang.org
assert_guess :filename => 'foo.hh', :source => 'foo'

# Arduino stuff
assert_guess :filename => 'foo.pde'
assert_guess :filename => 'foo.ino'
Expand Down
20 changes: 20 additions & 0 deletions spec/lexers/hack_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*- #

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

describe 'guessing' do
include Support::Guessing


it 'Guesses .php and .hh files that contain Hack code' do
assert_guess :filename => 'foo.php', :source => '<?hh // strict'
assert_guess :filename => 'foo.hh', :source => '<?hh // strict'
end

it 'Does not guess .php or .hh files that contain non-hack code' do
deny_guess :filename => 'foo.php', :source => '<? foo();'
deny_guess :filename => 'foo.hh', :source => '#include <foo.h>'
end
end
end
27 changes: 27 additions & 0 deletions spec/lexers/php_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*- #

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

describe 'guessing' do
include Support::Guessing

it 'Guesses files containing <?php' do
assert_guess :source => '<?php foo();'
end

it 'Guesses PHP files that do not contain Hack code' do
assert_guess :filename => 'foo.php', :source => '<? foo();'
end

it 'Guesses .php files containing <?, but not hack code' do
deny_guess :filenaame => 'foo.php', :source => '<?hh // strict'
end

it "Does not guess files containing <?hh" do
deny_guess :source => '<?hh foo();'
deny_guess :source => '<?hh // strict'
deny_guess :filename => '.php', :source => '<?hh foo();'
end
end
end
27 changes: 27 additions & 0 deletions spec/visual/samples/hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?hh // strict

// Unlike PHP, there's no top-level content (e.g. HTML) before `<?hh`, and `?>` is unsupported.

namespace Foo\Bar;

async function main(Vector<string> $argv): Awaitable<void> {
var_dump($argv->map($x ==> 'Arg: '.$x."\n"));
}

// vec: new by-value container
// Vector: old by-ref (object semantics) container
async function wrap(vec<string> $argv): Awaitable<void> {
await main(new Vector($argv));
}

function foo(int $x): string {
// UNSAFE
return $x;
}

function bar(int $x): string {
return /* UNSAFE_EXPR */ $x;
}

/* HH_FIXME[1002] pseudomain in strict file */
HH\Asio\join(main());

0 comments on commit db7cf94

Please sign in to comment.