Add lex/bison parser for theme.

This commit is contained in:
Dave Davenport 2016-12-09 19:49:49 +01:00
parent ea28bcdc74
commit d18f037d1c
8 changed files with 455 additions and 0 deletions

View File

@ -3,6 +3,8 @@ AUTOMAKE_OPTIONS = 1.11.3
ACLOCAL_AMFLAGS = -I libgwater ${ACLOCAL_FLAGS}
AM_YFLAGS = -d
noinst_LIBRARIES =
include $(top_srcdir)/libgwater-xcb-nolibtool.mk
@ -27,6 +29,7 @@ rofi_SOURCES=\
source/helper.c\
source/timings.c\
source/history.c\
source/theme.c\
source/widgets/box.c\
source/widgets/widget.c\
source/widgets/textbox.c\
@ -43,6 +46,8 @@ rofi_SOURCES=\
source/dialogs/window.c\
source/dialogs/script.c\
source/dialogs/help-keys.c\
lexer/theme-parser.y\
lexer/theme-lexer.l\
include/xcb.h\
include/xcb-internal.h\
include/rofi.h\
@ -55,6 +60,7 @@ rofi_SOURCES=\
include/helper.h\
include/timings.h\
include/history.h\
include/theme.h\
include/widgets/box.h\
include/widgets/widget.h\
include/widgets/widget-internal.h\

View File

@ -3,6 +3,10 @@ AC_INIT([rofi], [1.2.0], [https://github.com/DaveDavenport/rofi/],[],[https://fo
AC_CONFIG_SRCDIR([source/rofi.c])
AC_CONFIG_HEADER([config.h])
AC_PROG_LEX
AC_PROG_YACC
dnl ---------------------------------------------------------------------
dnl Setup automake to be silent and in foreign mode.
dnl We want xz distribution

67
doc/themer.md Normal file
View File

@ -0,0 +1,67 @@
Each widget has:
* Class: Type of widget.
Example: textbox, scrollbar, separator
Class are prefixed with a `@`
* Name: Internal name of the widget.
Sub-widgets are {Parent}.{Child}.
Example: Listview, Listview.Even, Listview.Uneven, Listview.Scrollbar
Names are prefixed with a `#`
* State: State of widget
Optional flag(s) indicating state.
Multiple flags can be set.
Example: Highlight Active Urgent
States are prefixed with a `!`
So to set color of Even entry in listview that is highlighted and urgent:
`@textbox #Listview.Even !Urgent !Highlight`
Or to indicate all textboxes
`@textbox !Highlight`
Class is manditory, name is optional. Name is split on .s.
# Internally:
The theme is represented like a tree:
class --> name --> name --> state -> state
The states are sorted alphabetically
So `@textbox #Listview.Even !Urgent !Highlight` becomes:
textbox->listview->even -> highlight -> urgent.
When searching for entries the tree is traversed until deepest node is found.
Missing states are skipped.
Then from there properties are searched going up again.
Properties are in the form of:
`name: value`
Each property ends with `;`
Each property has a type. (Boolean, Integer, String, Color)
A block is enclosed by `{}`
```
@textbox #Listview.Even !Urgent !Highlight {
padding: 3;
foreground: #aarrggbb;
}
```

46
include/theme.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef THEME_H
#define THEME_H
#include <glib.h>
typedef enum {
P_INTEGER,
P_FLOAT,
P_STRING,
P_BOOLEAN,
P_COLOR
} PropertyType;
typedef struct {
char *name;
PropertyType type;
union {
int i;
double f;
char *s;
int b;
unsigned int color;
} value;
} Property;
typedef struct _Widget {
char *name;
unsigned int num_widgets;
struct _Widget **widgets;
GHashTable *properties;
struct _Widget *parent;
} Widget;
extern Widget *rofi_theme;
Widget *rofi_theme_find_or_create_class ( Widget *base, const char *class );
void rofi_theme_print ( Widget *widget );
Property *rofi_theme_property_create ( PropertyType type );
void rofi_theme_property_free ( Property *p );
void rofi_theme_free ( Widget * );
void rofi_theme_parse_file ( const char *file );
#endif

33
lexer/theme-lexer.l Normal file
View File

@ -0,0 +1,33 @@
%option noyywrap
%{
#include <stdio.h>
#include "lexer/theme-parser.tab.h"
int yylex(void);
#define YY_DECL int yylex()
%}
%%
"@" { return CLASS;}
"\{" { return BOPEN;}
"\}" { return BCLOSE;}
":" { return PSEP; }
";" { return PCLOSE;}
"." { return NSEP; }
[ \t] ; // ignore all whitespace
[0-9]+\.[0-9]+ { yylval.fval = g_ascii_strtod(yytext, NULL); return T_FLOAT;}
[0-9]+ { yylval.ival = (int)g_ascii_strtoll(yytext, NULL, 10); return T_INT;}
(true|false) { yylval.bval= g_strcmp0(yytext, "true") == 0; return T_BOOLEAN;}
[a-zA-Z0-9]+ { yylval.sval = g_strdup(yytext); return N_STRING;}
\"[a-zA-Z0-9]+\" { yylval.sval = g_strdup(yytext); return T_STRING;}
#[0-9A-Fa-f]+ { yylval.colorval = (unsigned int)strtoull ( &yytext[1], NULL, 16); return T_COLOR;}
[\r\n]+ ;
<*><<EOF>> {
yyterminate();
}
%%

163
lexer/theme-parser.y Normal file
View File

@ -0,0 +1,163 @@
%locations
%debug
%error-verbose
%code requires {
#include "theme.h"
}
%{
#include <stdio.h>
#include <stdlib.h>
void yyerror(const char* s);
int yylex (void );
#include "theme.h"
Widget *rofi_theme = NULL;
%}
%union {
int ival;
double fval;
char *sval;
int bval;
unsigned int colorval;
Widget *theme;
GList *name_path;
Property *property;
GHashTable *property_list;
}
%token <ival> T_INT
%token <fval> T_FLOAT
%token <sval> T_STRING
%token <sval> N_STRING
%token <bval> T_BOOLEAN
%token <colorval> T_COLOR
%token CLASS "class";
%token BOPEN "bracket open";
%token BCLOSE "bracket close";
%token PSEP "property separator";
%token PCLOSE "property close";
%token NSEP "Name separator";
%type <sval> class
%type <sval> entry
%type <sval> pvalue
%type <theme> entries
%type <theme> start
%type <name_path> name_path
%type <property> property
%type <property_list> property_list
%type <property_list> properties
%type <property_list> optional_properties
%start start
%%
start:
optional_properties
entries {
$$ = $2;
if ( $1 != NULL ) {
$$->properties = $1;
}
}
;
entries:
%empty {
// There is always a base widget.
$$ = rofi_theme = (Widget*)g_malloc0 (sizeof(Widget));
rofi_theme->name = g_strdup ( "Window" );
}
| entries entry { $$ = $1; }
;
entry:
class
name_path
properties
{
Widget *widget = rofi_theme_find_or_create_class ( rofi_theme , $1 );
g_free($1);
for ( GList *iter = g_list_first ( $2 ); iter ; iter = g_list_next ( iter ) ) {
widget = rofi_theme_find_or_create_class ( widget, iter->data );
}
g_list_foreach ( $2, (GFunc)g_free , NULL );
g_list_free ( $2 );
if ( widget->properties != NULL ) {
fprintf(stderr, "Properties already set on this widget.\n");
exit ( EXIT_FAILURE );
}
widget->properties = $3;
};
/**
* properties
*/
optional_properties
: %empty { $$ = NULL; }
| property_list { $$ = $1; }
;
properties: BOPEN property_list BCLOSE { $$ = $2;}
| BOPEN BCLOSE { $$ = NULL; }
| %empty { $$ = NULL; }
;
property_list:
property {
$$ = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, (GDestroyNotify)rofi_theme_property_free );
g_hash_table_replace ( $$, $1->name, $1 );
}
| property_list property {
// Old will be free'ed, and key/value will be replaced.
g_hash_table_replace ( $$, $2->name, $2 );
}
;
property
: pvalue PSEP T_INT PCLOSE {
$$ = rofi_theme_property_create ( P_INTEGER );
$$->name = $1;
$$->value.i = $3;
}
| pvalue PSEP T_FLOAT PCLOSE {
$$ = rofi_theme_property_create ( P_FLOAT );
$$->name = $1;
$$->value.f = $3;
}
| pvalue PSEP T_COLOR PCLOSE {
$$ = rofi_theme_property_create ( P_COLOR );
$$->name = $1;
$$->value.color = $3;
}
| pvalue PSEP T_STRING PCLOSE {
$$ = rofi_theme_property_create ( P_STRING );
$$->name = $1;
$$->value.s = $3;
}
| pvalue PSEP T_BOOLEAN PCLOSE {
$$ = rofi_theme_property_create ( P_BOOLEAN );
$$->name = $1;
$$->value.b = $3;
}
;
pvalue: N_STRING { $$ = $1; }
class:
CLASS N_STRING { $$ = $2; }
;
name_path:
%empty { $$ = NULL; }
| N_STRING { $$ = g_list_append ( NULL, $1 );}
| name_path NSEP N_STRING { $$ = g_list_append ( $1, $3);}
;
%%

View File

@ -64,6 +64,8 @@
#include "gitconfig.h"
#include "theme.h"
// Pidfile.
char *pidfile = NULL;
const char *cache_dir = NULL;
@ -896,6 +898,12 @@ int main ( int argc, char *argv[] )
// Parse command line for settings, independent of other -no-config.
config_parse_cmd_options_dynamic ( );
char *theme = NULL;
if ( find_arg_str ( "-theme", &theme ) > 0 ){
rofi_theme_parse_file ( theme );
rofi_theme_print ( rofi_theme );
}
// Dump.
// catch help request
if ( find_arg ( "-h" ) >= 0 || find_arg ( "-help" ) >= 0 || find_arg ( "--help" ) >= 0 ) {

128
source/theme.c Normal file
View File

@ -0,0 +1,128 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "theme.h"
void yyerror ( const char *);
Widget *rofi_theme_find_or_create_class ( Widget *base, const char *class )
{
for ( unsigned int i = 0; i < base->num_widgets;i++){
if ( g_strcmp0(base->widgets[i]->name, class) == 0 ){
return base->widgets[i];
}
}
base->widgets = g_realloc ( base->widgets, sizeof(Widget*)*(base->num_widgets+1));
base->widgets[base->num_widgets] = g_malloc0(sizeof(Widget));
Widget *retv = base->widgets[base->num_widgets];
retv->parent = base;
retv->name = g_strdup(class);
base->num_widgets++;
return retv;
}
/**
* Properties
*/
Property *rofi_theme_property_create ( PropertyType type )
{
Property *retv = g_malloc0 ( sizeof(Property) );
retv->type = type;
return retv;
}
void rofi_theme_property_free ( Property *p )
{
if ( p == NULL ) {
return;
}
g_free ( p->name );
if ( p->type == P_STRING ) {
g_free ( p->value.s );
}
g_free(p);
}
void rofi_theme_free ( Widget *widget )
{
if ( widget == NULL ){
return;
}
if ( widget->properties ) {
g_hash_table_destroy ( widget->properties );
}
for ( unsigned int i = 0; i < widget->num_widgets; i++ ){
rofi_theme_free ( widget->widgets[i] );
}
g_free ( widget->widgets );
g_free ( widget->name );
g_free ( widget );
}
/**
* print
*/
static void rofi_theme_print_property_index ( int depth, Property *p )
{
printf("%*s %s: ", depth, "", p->name );
switch ( p->type )
{
case P_STRING:
printf("\"%s\"", p->value.s);
break;
case P_INTEGER:
printf("%d", p->value.i);
break;
case P_FLOAT:
printf("%.2f", p->value.f);
break;
case P_BOOLEAN:
printf("%s", p->value.b?"true":"false");
break;
case P_COLOR:
printf("#%08X", p->value.color);
break;
}
putchar ( '\n' );
}
static void rofi_theme_print_index ( int depth, Widget *widget )
{
printf ( "%*sName: %s \n", depth, "", widget->name );
GHashTableIter iter;
gpointer key, value;
if ( widget->properties ){
g_hash_table_iter_init (&iter, widget->properties);
while (g_hash_table_iter_next (&iter, &key, &value))
{
Property *p = (Property*)value;
rofi_theme_print_property_index ( depth, p );
}
}
for ( unsigned int i = 0; i < widget->num_widgets;i++){
rofi_theme_print_index ( depth+2, widget->widgets[i] );
}
}
void rofi_theme_print ( Widget *widget )
{
rofi_theme_print_index ( 0, widget);
}
extern int yyparse();
extern FILE* yyin;
extern Widget *rofi_theme;
void rofi_theme_parse_file ( const char *file )
{
yyin = fopen ( file, "rb");
if ( yyin == NULL ){
fprintf(stderr, "Failed to open file: '%s'\n", strerror ( errno ) );
return;
}
while ( yyparse() );
}
void yyerror(const char* s) {
fprintf(stderr, "Parse error: %s\n", s);
exit(EXIT_FAILURE);
}