mirror of
https://github.com/textmate/textmate.git
synced 2026-01-23 13:47:54 -05:00
244 lines
7.2 KiB
C++
244 lines
7.2 KiB
C++
#include "grammar.h"
|
||
#include "private.h"
|
||
#include <bundles/bundles.h>
|
||
#include <plist/plist.h>
|
||
#include <plist/schema.h>
|
||
#include <text/format.h>
|
||
#include <oak/oak.h>
|
||
#include <oak/debug.h>
|
||
|
||
OAK_DEBUG_VAR(Parser);
|
||
|
||
namespace parse
|
||
{
|
||
static rule_ptr parse_rule (plist::any_t const& plist);
|
||
|
||
static bool convert_array (plist::array_t const& array, std::vector<rule_ptr>& res)
|
||
{
|
||
iterate(it, array)
|
||
{
|
||
if(rule_ptr const& child = parse_rule(*it))
|
||
res.push_back(child);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool convert_dictionary (plist::dictionary_t const& dict, repository_ptr& res)
|
||
{
|
||
res.reset(new repository_t);
|
||
iterate(it, dict)
|
||
{
|
||
if(rule_ptr const& child = parse_rule(it->second))
|
||
res->insert(std::make_pair(it->first, child));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static rule_ptr parse_rule (plist::any_t const& any)
|
||
{
|
||
static plist::schema_t<rule_t> schema = plist::schema_t<rule_t>()
|
||
.map("name", &rule_t::scope_string )
|
||
.map("scopeName", &rule_t::scope_string )
|
||
.map("contentName", &rule_t::content_scope_string )
|
||
.map("match", &rule_t::match_string )
|
||
.map("begin", &rule_t::match_string )
|
||
.map("while", &rule_t::while_string )
|
||
.map("end", &rule_t::end_string )
|
||
.map("applyEndPatternLast", &rule_t::apply_end_last )
|
||
.map("include", &rule_t::include_string )
|
||
.map("patterns", &rule_t::children, &convert_array )
|
||
.map("captures", &rule_t::captures, &convert_dictionary)
|
||
.map("beginCaptures", &rule_t::begin_captures, &convert_dictionary)
|
||
.map("whileCaptures", &rule_t::while_captures, &convert_dictionary)
|
||
.map("endCaptures", &rule_t::end_captures, &convert_dictionary)
|
||
.map("repository", &rule_t::repository, &convert_dictionary)
|
||
.map("injections", &rule_t::injections, &convert_dictionary)
|
||
;
|
||
|
||
if(plist::dictionary_t const* plist = boost::get<plist::dictionary_t>(&any))
|
||
{
|
||
if(!plist->empty() && (plist->find("disabled") == plist->end() || !plist::get<int32_t>(plist->find("disabled")->second)))
|
||
{
|
||
rule_ptr res(new rule_t);
|
||
if(schema.convert(*plist, res.get()))
|
||
return res;
|
||
}
|
||
}
|
||
return rule_ptr();
|
||
}
|
||
|
||
static rule_ptr find_repository_item (rule_ptr const& rule, std::string const& name)
|
||
{
|
||
if(rule->repository)
|
||
{
|
||
repository_t::const_iterator it = rule->repository->find(name);
|
||
if(it != rule->repository->end())
|
||
return it->second;
|
||
}
|
||
return rule_ptr();
|
||
}
|
||
|
||
static bool pattern_has_back_references (std::string const& ptrn)
|
||
{
|
||
bool escape = false;
|
||
iterate(it, ptrn)
|
||
{
|
||
if(escape && isdigit(*it))
|
||
{
|
||
D(DBF_Parser, bug("%s: %s\n", ptrn.c_str(), "YES"););
|
||
return true;
|
||
}
|
||
escape = !escape && *it == '\\';
|
||
}
|
||
D(DBF_Parser, bug("%s: %s\n", ptrn.c_str(), "NO"););
|
||
return false;
|
||
}
|
||
|
||
// =============
|
||
// = grammar_t =
|
||
// =============
|
||
|
||
grammar_t::grammar_t (bundles::item_ptr const& grammarItem) : _item(grammarItem), _bundles_callback(*this)
|
||
{
|
||
bundles::add_callback(&_bundles_callback);
|
||
_rule = parse_rule(_item->plist());
|
||
_old_plist = _item->plist();
|
||
}
|
||
|
||
grammar_t::~grammar_t ()
|
||
{
|
||
bundles::remove_callback(&_bundles_callback);
|
||
}
|
||
|
||
oak::uuid_t const& grammar_t::uuid () const
|
||
{
|
||
static oak::uuid_t const FallbackGrammarUUID = oak::uuid_t().generate();
|
||
return _item ? _item->uuid() : FallbackGrammarUUID;
|
||
}
|
||
|
||
stack_ptr grammar_t::seed () const
|
||
{
|
||
return stack_ptr(new stack_t(_rule, _rule->scope_string));
|
||
}
|
||
|
||
void grammar_t::bundles_did_change ()
|
||
{
|
||
bundles::item_ptr newItem = bundles::lookup(uuid());
|
||
if(newItem && !plist::equal(_old_plist, newItem->plist())) // FIXME this is a kludge, ideally we should register as callback for the bundle item (when that is supported)
|
||
{
|
||
if(rule_ptr rule = parse_rule(newItem->plist()))
|
||
{
|
||
_item = newItem;
|
||
_rule = rule;
|
||
_old_plist = _item->plist();
|
||
resolve_includes();
|
||
|
||
_callbacks(&callback_t::grammar_did_change);
|
||
}
|
||
}
|
||
}
|
||
|
||
void grammar_t::resolve_includes ()
|
||
{
|
||
std::vector<rule_ptr> tmp;
|
||
resolve_includes(_rule, tmp);
|
||
}
|
||
|
||
void grammar_t::resolve_includes (rule_ptr rule, std::vector<rule_ptr>& stack)
|
||
{
|
||
std::string const& inc = rule->include_string;
|
||
if(inc != NULL_STR && inc != "$base")
|
||
{
|
||
D(DBF_Parser, bug("%s\n", inc.c_str()););
|
||
if(inc == "$self")
|
||
{
|
||
rule->include = stack.front();
|
||
}
|
||
else if(inc[0] == '#')
|
||
{
|
||
std::string const& name = inc.substr(1);
|
||
riterate(it, stack)
|
||
{
|
||
if(rule_ptr const& res = find_repository_item(*it, name))
|
||
{
|
||
rule->include = res;
|
||
D(DBF_Parser, bug("found include: %s\n", res->scope_string.c_str()););
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
std::string::size_type fragment = inc.find('#');
|
||
citerate(item, bundles::query(bundles::kFieldGrammarScope, fragment == std::string::npos ? inc : inc.substr(0, fragment), scope::wildcard, bundles::kItemTypeGrammar))
|
||
{
|
||
if(grammar_ptr grammar = parse_grammar(*item))
|
||
{
|
||
rule->include = fragment == std::string::npos ? grammar->_rule : find_repository_item(grammar->_rule, inc.substr(fragment+1));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(rule->include.expired())
|
||
fprintf(stderr, "*** couldn’t resolve %s\n", inc.c_str());
|
||
}
|
||
else
|
||
{
|
||
if(rule->match_string != NULL_STR)
|
||
{
|
||
rule->match_pattern = regexp::pattern_t(rule->match_string);
|
||
if(!rule->match_pattern)
|
||
fprintf(stderr, "bad begin/match pattern for %s\n", rule->scope_string.c_str());
|
||
}
|
||
if(rule->while_string != NULL_STR && !pattern_has_back_references(rule->while_string))
|
||
{
|
||
rule->while_pattern = regexp::pattern_t(rule->while_string);
|
||
if(!rule->while_pattern)
|
||
fprintf(stderr, "bad while pattern for %s\n", rule->scope_string.c_str());
|
||
}
|
||
if(rule->end_string != NULL_STR && !pattern_has_back_references(rule->end_string))
|
||
{
|
||
rule->end_pattern = regexp::pattern_t(rule->end_string);
|
||
if(!rule->end_pattern)
|
||
fprintf(stderr, "bad end pattern for %s\n", rule->scope_string.c_str());
|
||
}
|
||
}
|
||
|
||
stack.push_back(rule);
|
||
iterate(it, rule->children)
|
||
resolve_includes(*it, stack);
|
||
|
||
repository_ptr maps[] = { rule->repository, rule->captures, rule->begin_captures, rule->while_captures, rule->end_captures, rule->injections };
|
||
iterate(it, maps)
|
||
{
|
||
if(!*it)
|
||
continue;
|
||
|
||
iterate(mapIter, **it)
|
||
resolve_includes(mapIter->second, stack);
|
||
}
|
||
stack.pop_back();
|
||
}
|
||
|
||
// ==============
|
||
// = Public API =
|
||
// ==============
|
||
|
||
grammar_ptr parse_grammar (bundles::item_ptr const& grammarItem)
|
||
{
|
||
static std::map<oak::uuid_t, grammar_ptr> Cache;
|
||
injected_grammars(); // ensure these are loaded in the main thread
|
||
|
||
oak::uuid_t const& uuid = grammarItem->uuid();
|
||
std::map<oak::uuid_t, grammar_ptr>::iterator grammar = Cache.find(uuid);
|
||
if(grammar == Cache.end())
|
||
{
|
||
grammar = Cache.insert(std::make_pair(uuid, grammar_ptr(new grammar_t(grammarItem)))).first;
|
||
grammar->second->resolve_includes();
|
||
}
|
||
return grammar->second;
|
||
}
|
||
|
||
} /* parse */
|