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 Lexer for Bicep language #1937

Open
wants to merge 4 commits 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
10 changes: 10 additions & 0 deletions lib/rouge/demos/bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
targetScope = 'subscription' // To create a resource group

@description('The Azure region to create the resources in.')
param location string

// Create a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-sample'
location: location
}
112 changes: 112 additions & 0 deletions lib/rouge/lexers/bicep.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
module Rouge
module Lexers
class Bicep < Rouge::RegexLexer
tag 'bicep'
filenames '*.bicep'

title "Bicep"
desc 'Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources.'

def self.keywords
@keywords ||= Set.new %w(
as assert existing extends extension false for from func if import in metadata module
none null output param provider resource targetScope test true type using var void with
)
end

def self.datatypes
@datatypes ||= Set.new %w(array bool int object string)
end

def self.functions
@functions ||= Set.new %w(
array base64 base64ToJson base64ToString bool cidrHost cidrSubnet concat contains dataUri
dataUriToString dateTimeAdd dateTimeFromEpoch dateTimeToEpoch deployment empty endsWith
environment extensionResourceId filter first flatten format getSecret groupBy guid indexOf
int intersection items join json last lastIndexOf length listAccountSas listKeys listSecrets
loadFileAsBase64 loadJsonContent loadTextContent loadYamlContent managementGroup
managementGroupResourceId map mapValue max min newGuid objectKeys padLeft parseCidr pickZones
providers range readEnvironmentVariable reduce reference replace resourceGroup resourceId
shallowMerge skip sort split startsWith string subscription subscriptionResourceId substring take
tenant tenantResourceId toLower toObject toUpper trim union uniqueString uri uriComponent
uriComponentToString utcNow
)
end

operators = %w(+ - * / % < <= > >= == != =~ !~ && || ! ?? ... .?)

punctuation = %w(( ) { } [ ] , : ; = .)

state :root do
mixin :comments

# Match strings
rule %r/'/, Str::Single, :string

# Match numbers
rule %r/\b\d+\b/, Num

# Rules for sets of reserved keywords
rule %r/\b\w+\b/ do |m|
if self.class.keywords.include? m[0]
token Keyword
elsif self.class.datatypes.include? m[0]
token Keyword::Type
elsif self.class.functions.include? m[0]
token Name::Function
else
token Name
end
end

# Match operators
rule %r/#{operators.map { |o| Regexp.escape(o) }.join('|')}/, Operator

# Enter a state when encountering an opening curly bracket
rule %r/{/, Punctuation::Indicator, :block

# Match punctuation
rule %r/#{punctuation.map { |p| Regexp.escape(p) }.join('|')}/, Punctuation

# Match identifiers
rule %r/[a-zA-Z_]\w*/, Name

# Match decorators
rule %r/@[a-zA-Z_]\w*/, Name::Decorator

# Ignore whitespace
rule %r/\s+/, Text
end

state :comments do
rule %r(//[^\n\r]+), Comment::Single
rule %r(/\*.*?\*/)m, Comment::Multiline
end

state :string do
rule %r/[^'$}]+/, Str::Single
rule %r/\$(?!\{)/, Str::Single
rule %r/\$[\{]/, Str::Interpol, :interp
rule %r/\'/, Str::Single, :pop!
rule %r/\$+/, Str::Single
end

state :interp do
rule %r/\}/, Str::Interpol, :pop!
mixin :root
end

# State for matching code blocks between curly brackets
state :block do
# Match property names
rule %r/\b([a-zA-Z_]\w*)\b(?=\s*:)/, Name::Property

# Match closing curly brackets
rule %r/}/, Punctuation::Indicator, :pop!

# Include the root state for nested tokens
mixin :root
end
end
end
end
16 changes: 16 additions & 0 deletions spec/lexers/bicep_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

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

describe 'guessing' do
include Support::Guessing

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

32 changes: 32 additions & 0 deletions spec/visual/samples/bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Target scope can be set using the following values:
- resourceGroup (default)
- subscription
- managementGroup
- tenant
*/
targetScope = 'subscription' // To create a resource group

@description('The Azure region to create the resources in.')
param location string

var suffix = uniqueString(subscription().subscriptionId, 'my-project')

var someTags = [
{
key: 'location'
value: location
}
{
key: 'isTest'
value: true
}
]

// Create a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-${suffix}'
location: location

tags: toObject(someTags, tag => tag.key, tag => tag.?value)
}