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 a jq lexer #1863

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rouge/demos/jq
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.products[] | select(.price < 100) | .name
71 changes: 71 additions & 0 deletions lib/rouge/lexers/jq.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true


module Rouge
module Lexers
class JQ < RegexLexer
title 'jq'
desc 'The jq programming language (https://stedolan.github.io/jq/)'
tag 'jq'
filenames "*.jq"

def self.keywords
@keywords ||= Set.new %w(as import include module def if then else elif end reduce foreach try catch label break __loc__)
end

def self.word_operators
@word_operators ||= Set.new %w(and or)
end

state :root do
rule %r/#.*/, Comment::Single

rule %r(((\||\+|\-|\*|/|%|//)=|=)), Operator
rule %r((\!=|==|//|\<=|\>=|\?//|\+|\-|\*|\/|\%|\<|\>|\?|\.\.)), Operator

rule %r/(;|:|\||::|,)/, Punctuation
rule %r/([\[\(\{\}\)\]])/, Punctuation

rule %r/@[a-zA-Z0-9_]+/, Name::Function::Magic

rule %r/\.[a-zA-Z_][a-zA-Z_0-9]*/ do |m|
token Name::Attribute
end
rule %r/\./, Name::Attribute

rule %r/\$[a-zA-Z_][a-zA-Z_0-9]*/ do |m|
token Name::Variable
end

rule %r/[a-zA-Z_][a-zA-Z_0-9]*/ do |m|
if self.class.word_operators.include?(m[0])
token Operator::Word
elsif self.class.keywords.include?(m[0])
token Keyword
else
token Name
end
end

rule %r/[0-9.]+([eE][+-]?[0-9]+)?/, Literal::Number
rule %r/\"/, Literal::String, :string

rule %r/\s+/, Text
end

state :string do
rule %r/[^"\\]+/, Literal::String
rule %r/\\"/, Literal::String::Double
rule %r/(\\[^u(]|\\u[a-zA-Z0-9]{0,4})/, Literal::String::Escape
rule %r/\\\(/, Punctuation, :string_interpolation
rule %r/"/, Literal::String, :pop!
end

state :string_interpolation do
rule %r/\)/, Punctuation, :pop!
mixin :root
end
end
end
end
27 changes: 27 additions & 0 deletions spec/lexers/jq_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

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

describe 'guessing' do
include Support::Guessing

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

describe 'lexer' do
include Support::Lexing

it 'lexes string interpolation' do
assert_tokens_equal '"value: \(.value)"',
['Literal.String', '"value: '],
['Punctuation', '\('],
['Name.Attribute', '.value'],
['Punctuation', ')'],
['Literal.String', '"']
end
end
end
89 changes: 89 additions & 0 deletions spec/visual/samples/jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# import modules
import "mymodule" as foo;
include "bar";

# define functions
def add($a; $b): $a + $b;

# error handling
def handle_error:
try .a catch ". is not an object";

# literals
def literals:
null as $null |
true as $t |
false as $f |
1.23 as $num |
1.23e8 as $num_index |
1.23e-8 as $num_index2 |
"abc" as $str |
[1, "a", [2, 3]] as $arr |
{"num": 1, "str": 2, "obj": {"arr": [3]}} as $obj |
.;

def string_interpolation($a; $b):
"\($a) + \($b) = \($a + $b)";

def escape_sequences:
"\u03bc == \"μ\"\n";

# operators
def operators:
2 != 3 |
2 == 3 |
2 < 3 |
2 > 3 |
2 <= 3 |
2 >= 3 |
2 + 3 |
2 - 3 |
2 * 3 |
2 / 3 |
2 % 3 |
2 // 3 ;

def destructuring_alternative_operator:
.a as [$a, $b] ?// $a | .;

def boolean_operators:
2 and 3 |
2 or 3;

# control flows
def if_expression:
if . == 0 then "zero" elif . == 1 then "one" else "many" end;

def accumulate:
foreach range(1; 10) as $i (0; . + $i; .);

def sum:
reduce .[] as $item (0; . + $item);

def inverses:
label $out | reduce .[] as $item ([]; if $item == 0 then break $out else . + [1 / $item] end);

def doubles:
[while(.<100; .*2)];

# object indexing
def indexing:
.foo[0]["bar"][0:2] | ..;

# assignments
def assignments:
. = 2 |
. += 2 |
. -= 2 |
. *= 2 |
. /= 2 |
. %= 2 |
. //= 2 |
. |= 2;

# format strings
def escape_for_html:
. | @html;

# expression with pipes
.values[0].foo | add(.a; .b), .bar?