[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH] pokefmt: add template system to embed Poke code in files
From: |
Jose E. Marchesi |
Subject: |
Re: [PATCH] pokefmt: add template system to embed Poke code in files |
Date: |
Sat, 23 Sep 2023 12:14:32 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) |
> 2023-09-22 Mohammad-Reza Nabipoor <mnabipoor@gnu.org>
>
> * configure.ac (AC_CONFIG_FILES): Add `pokefmt/Makefile'.
> * Makefile.am (SUBDIRS): Add `pokefmt'.
> * pokefmt/pokefmt.l: New template system to embed Poke code in docs,
> tests, etc.
> * pokefmt/pokefmt.pk: Poke support code for `pokefmt' app.
> * pokefmt/Makefile.am: New file.
> * run.in (POKEFMTAPPDIR): New variable.
> * poke.pokefmt/pokefmt.exp: New tests.
> * testsuite/Makefile.am (check-DEJAGNU): Add `POKEFMTAPPDIR' env var.
> (EXTRA_DIST): Update.
> * doc/poke.texi (pokefmt): Add new chapter.
> ---
>
> Hello Jose.
>
> This is `pokefmt' with tests and documentation!
>
>
> Regards,
> Mohammad-Reza
>
>
> ChangeLog | 14 +
> Makefile.am | 2 +-
> configure.ac | 1 +
> doc/poke.texi | 88 ++++
> pokefmt/Makefile.am | 83 ++++
> pokefmt/pokefmt.l | 623 +++++++++++++++++++++++++++++
> pokefmt/pokefmt.pk | 47 +++
> run.in | 1 +
> testsuite/Makefile.am | 2 +
> testsuite/poke.pokefmt/pokefmt.exp | 115 ++++++
> 10 files changed, 975 insertions(+), 1 deletion(-)
> create mode 100644 pokefmt/Makefile.am
> create mode 100644 pokefmt/pokefmt.l
> create mode 100644 pokefmt/pokefmt.pk
> create mode 100644 testsuite/poke.pokefmt/pokefmt.exp
>
> diff --git a/ChangeLog b/ChangeLog
> index 00455319..2b834e32 100644
> --- a/ChangeLog
> +++ b/ChangeLog
> @@ -1,3 +1,17 @@
> +2023-09-22 Mohammad-Reza Nabipoor <mnabipoor@gnu.org>
> +
> + * configure.ac (AC_CONFIG_FILES): Add `pokefmt/Makefile'.
> + * Makefile.am (SUBDIRS): Add `pokefmt'.
> + * pokefmt/pokefmt.l: New template system to embed Poke code in docs,
> + tests, etc.
> + * pokefmt/pokefmt.pk: Poke support code for `pokefmt' app.
> + * pokefmt/Makefile.am: New file.
> + * run.in (POKEFMTAPPDIR): New variable.
> + * poke.pokefmt/pokefmt.exp: New tests.
> + * testsuite/Makefile.am (check-DEJAGNU): Add `POKEFMTAPPDIR' env var.
> + (EXTRA_DIST): Update.
> + * doc/poke.texi (pokefmt): Add new chapter.
> +
> 2023-09-18 Jose E. Marchesi <jemarch@gnu.org>
>
> * libpoke/pkl-lex.l: Accept t and T suffix for uint<1> values.
> diff --git a/Makefile.am b/Makefile.am
> index dec75aac..34c50b80 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -17,7 +17,7 @@
>
> ACLOCAL_AMFLAGS = -I m4 -I m4/libpoke
> SUBDIRS = jitter gl autoconf maps pickles gl-libpoke libpoke poke \
> - poked utils doc man testsuite etc po
> + poked pokefmt utils doc man testsuite etc po
>
> EXTRA_DIST = INSTALL.generic DEPENDENCIES $(top_srcdir)/.version
> BUILT_SOURCES = $(top_srcdir)/.version
> diff --git a/configure.ac b/configure.ac
> index 6811f4d8..5bb9835a 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -277,6 +277,7 @@ AC_CONFIG_FILES(Makefile
> libpoke/pkl-config.pk
> poke/Makefile
> poked/Makefile
> + pokefmt/Makefile
> utils/Makefile
> pickles/Makefile
> maps/Makefile
> diff --git a/doc/poke.texi b/doc/poke.texi
> index 136b10d0..3fc5df8b 100644
> --- a/doc/poke.texi
> +++ b/doc/poke.texi
> @@ -77,6 +77,9 @@ Pickles
> poked
> * poked:: The poke daemon.
>
> +pokefmt
> +* pokefmt:: The poke template system.
> +
> poke and Emacs
> * Programming Emacs Modes:: Modes to write Poke and RAS programs.
>
> @@ -8581,6 +8584,91 @@ An array of callbacks to be run after executing the
> @emph{command}
> (received from input channel 2). Callback signature: @code{()void}.
> @end table
>
> +@node pokefmt
> +@chapter pokefmt
> +
> +@code{pokefmt} is a template system to embed Poke code in files.
> +It copies the content from standard input to standard output and
> +replaces the Poke code with the result of execution. You can write
> +Poke code between @code{%@{} and @code{@}%} delimters. To simplify
s/delimters/delimiters/.
> +printing of expressions, @code{pokefmt} also supports embedding of
> +Poke expressions between @code{%(} and @code{%)} delimiters.
> +
> +
> +As as example, @code{pokefmt} will transform the following text
> +
> +@example
> +%@{
> + load riscv;
> +
> + var a = 1, a = 2;
> +@}%
> +The encoding of ADD R3, R2, R1 instruction in RISC-V instruction set
> +is %@{printf "0x%u32x", rv32_add (3, 2, 1);@}%.
> +
> +The result of %(a)% + %(b)% is %(a + b)%.
> +@end example
> +
> +to this text:
> +
> +@example
> +
> +The encoding of ADD R3, R2, R1 instruction in RISC-V instruct set
> +is 0x001101b3.
> +
> +The result of 1 + 2 is 3.
> +@end example
> +
> +
> +You can customize the printer for expressions by re-defining
> +@code{pokefmt_expr_printer} function. For example,
> +
> +
> +@example
> +#as: -march=rv32i
> +#objdump: -dr
> +%@{
> + load riscv;
> + fun pokefmt_expr_printer = (any i32) void:
> + @{
> + printf ("%u32x", i32 as uint<32>);
> + @}
> +@}%
> +.*:[ ]+file format .*
> +
> +
> +Disassembly of section .text:
> +
> +0+000 <target>:
> +#...
> +[ ]+40:[ ]+%(+(rv32_auipc :rd 0 :imm 0))%[ ]+auipc[ ]+zero,0x0
> +%@{
> + /* Change the expression printer to accept RV32_Insn. */
> + fun pokefmt_expr_printer = (any rv32insn) void:
> + @{
> + printf ("%u32x", +(rv32insn as RV32_Insn));
> + @}
> +@}%[ ]+44:[ ]+%(rv32_lw :rd 0 :rs1 0 :imm 0)%[ ]+lw[
> ]+zero,0\(zero\) # 0 .*
> +@end example
> +
> +will be transformed to:
> +
> +@example
> +#as: -march=rv32i
> +#objdump: -dr
> +
> +.*:[ ]+file format .*
> +
> +
> +Disassembly of section .text:
> +
> +0+000 <target>:
> +#...
> +[ ]+40:[ ]+00000017[ ]+auipc[ ]+zero,0x0
> +[ ]+44:[ ]+00002003[ ]+lw[ ]+zero,0\(zero\) # 0 .*
> +@end example
> +
> +
> @node Programming Emacs Modes
> @chapter Programming Emacs Modes
>
> diff --git a/pokefmt/Makefile.am b/pokefmt/Makefile.am
> new file mode 100644
> index 00000000..8f75b166
> --- /dev/null
> +++ b/pokefmt/Makefile.am
> @@ -0,0 +1,83 @@
> +# pokefmt Makefile.am
> +
> +# Copyright (C) 2023 Mohammad-Reza Nabipoor
> +
> +# This program is free software: you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation, either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
> +
> +AUTOMAKE_OPTIONS = subdir-objects
> +
> +MOSTLYCLEANFILES =
> +CLEANFILES =
> +DISTCLEANFILES =
> +MAINTAINERCLEANFILES =
> +
> +BUILT_SOURCES =
> +
> +EXTRA_DIST =
> +
> +appdir = $(pkgdatadir)/pokefmt
> +
> +dist_app_DATA = pokefmt.pk
> +
> +AM_LFLAGS = -d
> +# The Automake generated .l.c rule is broken: When executed in a VPATH build,
> +#+ - The .c file gets generated in the build directory. But since it
> requires
> +# special tools to rebuild it, we need to distribute it in the tarballs,
> +# and by the GNU Coding Standards
> +# <https://www.gnu.org/prep/standards/html_node/Makefile-Basics.html>
> +# the file should be generated in the source directory.
> +# - The #line directives in the .c file refer to a nonexistent file once it
> +# has been moved from the build directory to the source directory. This
> +# leads to error if 'lcov' is used later.
> +# Additionally, here we assume Flex and therefore don't need the ylwrap
> script.
> +# Therefore we override this rule.
> +# Since this is a rule that produces multiple files, we apply the idiom from
> +# <https://lists.gnu.org/archive/html/bug-make/2020-09/msg00008.html>, so
> that
> +# it works also in parallel 'make'.
> +generate-pokefmt:
> + $(AM_V_LEX)$(LEX) $(LFLAGS) $(AM_LFLAGS) -t $(srcdir)/pokefmt.l >
> pokefmt.c \
> + && test ':' = '$(LEX)' || { \
> + sed -e 's|".*/pokefmt\.l"|"pokefmt.l"|' \
> + -e 's|"lex\.yy\.c"|"pokefmt.c"|' \
> + < pokefmt.c > pokefmt.c-tmp \
> + && sed -e 's|".*/pokefmt\.l"|"pokefmt.l"|' \
> + < pokefmt.h > pokefmt.h-tmp \
> + && rm -f pokefmt.c pokefmt.h \
> + && mv pokefmt.c-tmp $(srcdir)/pokefmt.c \
> + && mv pokefmt.h-tmp $(srcdir)/pokefmt.h; \
> + }
> +.PHONY: generate-pokefmt
> +# The above rule will generate files with time-stamp order
> +# pokefmt.l <= pokefmt.c <= pokefmt.h.
> +pokefmt.c: pokefmt.l
> + @{ test -f $(srcdir)/pokefmt.c && test ! $(srcdir)/pokefmt.c -ot
> $(srcdir)/pokefmt.l; } || $(MAKE) generate-pokefmt
> +pokefmt.h: pokefmt.c
> + @{ test -f $(srcdir)/pokefmt.h && test ! $(srcdir)/pokefmt.h -ot
> $(srcdir)/pokefmt.c; } || $(MAKE) generate-pokefmt
> +BUILT_SOURCES += pokefmt.c pokefmt.h
> +MOSTLYCLEANFILES += pokefmt.c-tmp pokefmt.h-tmp
> +MAINTAINERCLEANFILES += $(srcdir)/pokefmt.c $(srcdir)/pokefmt.h
> +EXTRA_DIST += pokefmt.l pokefmt.c pokefmt.h
> +
> +bin_PROGRAMS = pokefmt
> +pokefmt_SOURCES = pokefmt.c pokefmt.h
> +
> +pokefmt_SOURCES += ../common/pk-utils.c ../common/pk-utils.h
> +
> +pokefmt_CPPFLAGS = -I$(top_srcdir)/common \
> + -I$(top_builddir)/gl -I$(top_srcdir)/gl \
> + -I$(top_srcdir)/libpoke -I$(top_builddir)/libpoke
> +pokefmt_CFLAGS = -Wall
> +pokefmt_LDADD = $(top_builddir)/gl/libgnu.la \
> + $(top_builddir)/libpoke/libpoke.la
Using the same gnulib module than the poke application is ok I guess,
even if it will probably bring in a lot of stuff that pokefmt doesn't
need.
> +pokefmt_LDFLAGS =
> diff --git a/pokefmt/pokefmt.l b/pokefmt/pokefmt.l
> new file mode 100644
> index 00000000..fffda29f
> --- /dev/null
> +++ b/pokefmt/pokefmt.l
> @@ -0,0 +1,623 @@
> +
> +/* pokefmt.l - Template system to embed Poke code in files. */
> +
> +/* Copyright (C) 2023 Mohammad-Reza Nabipoor. */
> +
> +/* This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +/* clang-format off */
> +
> +%option noyywrap 8bit warn nodefault noinput nounput reentrant
> +%option outfile="pokefmt.c"
> +%option header-file="pokefmt.h"
> +%option extra-type="struct source_range *"
> +
> +%top{
> +#include <config.h>
> +}
> +
> +%x POKE_STMT POKE_EXPR
> +
> +%{
> +#define POKE_STMT_PARTIAL (POKE_EXPR + 1)
> +#define POKE_EXPR_PARTIAL (POKE_EXPR + 2)
> +%}
> +
> +%{
> +/* line[1], col[1] pair represents the current location.
> + line[0], col[0] pair represents the beginning of current/previous
> + Poke code section.
> +
> + The range is a half-open interval [(l0,c0), (l1,c1)). */
> +struct source_range
> +{
> + int line[2];
> + int col[2];
> +};
> +%}
> +
> +%%
> +
> +"\\%{" {
> + yyextra->col[1] += 3;
> + fwrite (yytext + 1, (size_t) yyleng - 1, 1, yyout);
> + }
> +"\\%(" {
> + yyextra->col[1] += 3;
> + fwrite (yytext + 1, (size_t) yyleng - 1, 1, yyout);
> + }
> +
> +"%{" {
> + yyextra->line[0] = yyextra->line[1];
> + yyextra->col[0] = yyextra->col[1];
> + yyextra->col[1] += 2;
> + BEGIN (POKE_STMT);
> + }
> +"%(" {
> + yyextra->line[0] = yyextra->line[1];
> + yyextra->col[0] = yyextra->col[1];
> + yyextra->col[1] += 2;
> + BEGIN (POKE_EXPR);
> + }
> +
> +<POKE_STMT>{
> +
> +[^}\n]* |
> +"}"+[^}%\n]* {
> + yyextra->col[1] += yyleng;
> + return POKE_STMT_PARTIAL;
> + }
> +\n+ {
> + yyextra->line[1] += yyleng;
> + yyextra->col[1] = 1;
> + return POKE_STMT_PARTIAL;
> + }
> +"}"+"%" {
> + yyextra->col[1] += yyleng;
> + BEGIN (INITIAL);
> + return POKE_STMT;
> + }
> +
> +}
> +
> +<POKE_EXPR>{
> +
> +[^)\n]* |
> +")"+[^)%\n]* {
> + yyextra->col[1] += yyleng;
> + return POKE_EXPR_PARTIAL;
> + }
> +\n+ {
> + yyextra->line[1] += yyleng;
> + yyextra->col[1] = 1;
> + return POKE_EXPR_PARTIAL;
> + }
> +")"+"%" {
> + yyextra->col[1] += yyleng;
> + BEGIN (INITIAL);
> + return POKE_EXPR;
> + }
> +
> +}
> +
> +. {
> + ++yyextra->col[1];
> + ECHO;
> + }
> +\n {
> + ++yyextra->line[1];
> + yyextra->col[1] = 1;
> + ECHO;
> + }
> +
> +%%
> +
> +// clang-format on
> +
> +#include <assert.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <err.h>
> +#include <getopt.h>
> +#include <unistd.h>
> +
> +#include "configmake.h"
> +#include "libpoke.h"
> +#include "pk-utils.h"
> +
> +struct poke
> +{
> + pk_compiler compiler;
> + FILE *output;
> + char *filename;
> +};
> +
> +void poke_init (struct poke *, const char *poke_src_file, FILE *output);
> +void poke_free (struct poke *);
> +
> +static void poke_stmt (struct poke *, const char *poke_src,
> + const struct source_range *);
> +static void poke_expr (struct poke *, const char *poke_src,
> + const struct source_range *);
> +
> +static struct pokefmt_opts
> +{
> + /* Poke codes provided by user on the command line. */
> + size_t n_codes;
> + char **codes;
> +} pokefmt_opts;
> +
> +static void pokefmt_opts_init (int argc, char *argv[]);
> +static void pokefmt_opts_free ();
> +
> +int
> +main (int argc, char *argv[])
> +{
> + struct poke poke;
> + yyscan_t scanner;
> + int section_type;
> + struct source_range range = {
> + .line = { 1, 1 },
> + .col = { 1, 1 },
> + };
> + char *poke_src;
> + size_t poke_src_len;
> + size_t poke_src_cap;
> +
> + pokefmt_opts_init (argc, argv);
> +
> + poke_src_len = 0;
> + poke_src_cap = 1024;
> + poke_src = malloc (poke_src_cap);
> + if (poke_src == NULL)
> + err (1, "malloc() failed");
> +
> +#define SRC_APPEND(src, srclen)
> \
> + do
> \
> + {
> \
> + size_t _srclen = (srclen);
> \
> + size_t _rem = poke_src_cap - poke_src_len;
> \
> + if (_rem < _srclen + 1)
> \
> + {
> \
> + poke_src_cap += _srclen + 1024;
> \
> + poke_src = realloc (poke_src, poke_src_cap);
> \
> + if (poke_src == NULL)
> \
> + err (1, "realloc() failed");
> \
> + }
> \
> + memcpy (poke_src + poke_src_len, (src), _srclen);
> \
> + poke_src_len += _srclen;
> \
> + poke_src[poke_src_len] = '\0';
> \
> + }
> \
> + while (0)
> +
> + poke_init (&poke, /*FIXME*/ NULL, stdout);
> +
> + if (yylex_init_extra (&range, &scanner) != 0)
> + err (1, "yylex_init_extra() failed");
> +
> + pokefmt_opts_free ();
> +
> + while ((section_type = yylex (scanner)))
> + {
> + char *chunk;
> + int chunk_len;
> +
> + switch (section_type)
> + {
> + case POKE_STMT:
> + case POKE_EXPR:
> + case POKE_STMT_PARTIAL:
> + case POKE_EXPR_PARTIAL:
> + chunk = yyget_text (scanner);
> + chunk_len = yyget_leng (scanner);
> + break;
> + default:
> + PK_UNREACHABLE ();
> + }
> +
> + switch (section_type)
> + {
> + case POKE_STMT:
> + case POKE_EXPR:
> + chunk_len -= 2; /* Skip "}%" or ")%". */
> + }
> + SRC_APPEND (chunk, chunk_len);
> +
> + switch (section_type)
> + {
> + case POKE_STMT:
> + poke_stmt (&poke, poke_src, &range);
> + poke_src_len = 0;
> + break;
> + case POKE_EXPR:
> + SRC_APPEND (";", 1);
> + poke_expr (&poke, poke_src, &range);
> + poke_src_len = 0;
> + break;
> + }
> + }
> +
> + free (poke_src);
> + yylex_destroy (scanner);
> + poke_free (&poke);
> + return 0;
> +}
> +
> +static void
> +pokefmt_version (void)
> +{
> + printf ("pokefmt (GNU poke) %s\n\n", VERSION);
> + printf ("\
> +Copyright (C) %s The poke authors.\n\
> +License GPLv3+: GNU GPL version 3 or later",
> + "2023");
> + puts (".\n\
> +This is free software: you are free to change and redistribute it.\n\
> +There is NO WARRANTY, to the extent permitted by law.");
> +}
> +
> +// clang-format off
> +
> +static void
> +pokefmt_help (void)
> +{
> + printf ("Usage: pokefmt [OPTION]... [POKE-CODE]..." "\n"
> + "Template system to process embedded code in files." "\n"
> + "" "\n"
> + " -h, --help print a help message and exit" "\n"
> + " -v, --version show version and exit" "\n"
> + "" "\n"
> + "Report bugs in the bug tracker at" "\n"
> + " %s" "\n"
> + " or by email to <%s>." "\n",
> + PACKAGE_BUGZILLA, PACKAGE_BUGREPORT);
> +
> +#ifdef PACKAGE_PACKAGER_BUG_REPORTS
> + printf ("Report %s bugs to: %s\n", PACKAGE_PACKAGER,
> + PACKAGE_PACKAGER_BUG_REPORTS);
> +#endif
> + printf ("%s home page: <%s>" "\n"
> +"General help using GNU software: <http://www.gnu.org/gethelp/>\n",
> + PACKAGE_NAME, PACKAGE_URL);
> +}
> +
> +// clang-format on
> +
> +static void
> +pokefmt_opts_init (int argc, char *argv[])
> +{
> + enum
> + {
> + OPT_HELP = 256,
> + OPT_VERSION,
> + };
> + static const struct option options[] = {
> + { "help", no_argument, NULL, OPT_HELP },
> + { "version", no_argument, NULL, OPT_VERSION },
> + };
> + int ret;
> +
> + while ((ret = getopt_long (argc, argv, "hv", options, NULL)) != -1)
> + {
> + switch (ret)
> + {
> + case OPT_HELP:
> + case 'h':
> + pokefmt_help ();
> + exit (EXIT_SUCCESS);
> + break;
> + case OPT_VERSION:
> + case 'v':
> + pokefmt_version ();
> + exit (EXIT_SUCCESS);
> + break;
> + default:
> + pokefmt_help ();
> + exit (EXIT_FAILURE);
> + }
> + }
> +
> + if (optind < argc)
> + {
> + pokefmt_opts.n_codes = argc - optind;
> + pokefmt_opts.codes = malloc (pokefmt_opts.n_codes * sizeof (char *));
> + if (pokefmt_opts.codes == NULL)
> + err (1, "malloc() failed");
> +
> + for (size_t i = 0; i < pokefmt_opts.n_codes; ++i)
> + {
> + char *p;
> +
> + if (asprintf (&p, "%s;", argv[optind++]) == -1)
> + err (1, "asprintf() failed");
> + pokefmt_opts.codes[i] = p;
> + }
> + }
> +}
> +
> +static void
> +pokefmt_opts_free ()
> +{
> + for (size_t i = 0; i < pokefmt_opts.n_codes; ++i)
> + free (pokefmt_opts.codes [i]);
> + free (pokefmt_opts.codes);
> + pokefmt_opts.n_codes = 0;
> +}
> +
> +//--- poke
> +
> +// terminal IO functions
> +static void
> +tif_flush (void)
> +{
> + fflush (stdout);
> +}
> +static void
> +tif_puts (const char *s)
> +{
> + printf ("%s", s);
> +}
> +static void
> +tif_printf (const char *fmt, ...)
> +{
> + va_list ap;
> +
> + va_start (ap, fmt);
> + vprintf (fmt, ap);
> + va_end (ap);
> +}
> +static void
> +tif_indent (unsigned int level, unsigned int step)
> +{
> + putchar ('\n');
> + for (unsigned int i = 0; i < step * level; ++i)
> + putchar (' ');
> +}
> +static void
> +tif_class (const char *name)
> +{
> + (void)name;
> +}
> +static int
> +tif_class_end (const char *name)
> +{
> + (void)name;
> + return 1;
> +}
> +static void
> +tif_hlink (const char *name, const char *id)
> +{
> + (void)name;
> + (void)id;
> +}
> +static int
> +tif_hlink_end (void)
> +{
> + return 1;
> +}
> +static struct pk_color
> +tif_color (void)
> +{
> + static struct pk_color c = {
> + .red = 0,
> + .green = 0,
> + .blue = 0,
> + };
> + return c;
> +}
> +static struct pk_color
> +tif_bgcolor (void)
> +{
> + static struct pk_color c = {
> + .red = 255,
> + .green = 255,
> + .blue = 255,
> + };
> + return c;
> +}
> +static void
> +tif_color_set (struct pk_color c)
> +{
> + (void)c;
> +}
> +static void
> +tif_bgcolor_set (struct pk_color c)
> +{
> + (void)c;
> +}
> +
> +static void
> +default_exception_handler (struct poke *poke, pk_val exc);
> +
> +void
> +poke_init (struct poke *pk, const char *poke_src_file, FILE *output)
> +{
> + static struct pk_term_if tif = {
> + .flush_fn = tif_flush,
> + .puts_fn = tif_puts,
> + .printf_fn = tif_printf,
> + .indent_fn = tif_indent,
> + .class_fn = tif_class,
> + .end_class_fn = tif_class_end,
> + .hyperlink_fn = tif_hlink,
> + .end_hyperlink_fn = tif_hlink_end,
> + .get_color_fn = tif_color,
> + .get_bgcolor_fn = tif_bgcolor,
> + .set_color_fn = tif_color_set,
> + .set_bgcolor_fn = tif_bgcolor_set,
> + };
> + int ret;
> + pk_val pexc;
> +
> + pk->filename = strdup ("<stdin>");
> + pk->output = output;
> + pk->compiler = pk_compiler_new (&tif);
> + if (pk->compiler == NULL)
> + errx (1, "pk_compiler_new() failed");
> +
> + /* Add load paths to the incremental compiler. */
> + {
> + pk_val load_path = pk_decl_val (pk->compiler, "load_path");
> + const char *poke_datadir = getenv ("POKEDATADIR");
> + const char *poke_picklesdir = getenv ("POKEPICKLESDIR");
> + const char *poke_load_path = getenv ("POKE_LOAD_PATH");
> + char *pokefmt_appdir = getenv ("POKEFMTAPPDIR");
> + char *user_load_path = NULL;
> + char *new_path;
> +
> + if (poke_datadir == NULL)
> + poke_datadir = PKGDATADIR;
> + if (pokefmt_appdir == NULL)
> + pokefmt_appdir = "%DATADIR%/pokefmt";
> + if (poke_picklesdir == NULL)
> + poke_picklesdir = "%DATADIR%/pickles";
> + if (poke_load_path)
> + {
> + user_load_path = pk_str_concat (poke_load_path, ":", NULL);
> + if (user_load_path == NULL)
> + err (1, "pk_str_concat() failed");
> + }
> +
> + new_path = pk_str_concat (user_load_path ? user_load_path : "",
> + pk_string_str (load_path), ":", pokefmt_appdir,
> + ":", poke_picklesdir, NULL);
> + if (new_path == NULL)
> + err (1, "pk_str_concat() failed");
> + pk_decl_set_val (pk->compiler, "load_path", pk_make_string (new_path));
> +
> + free (new_path);
> + free (user_load_path);
> + }
> +
> + if (pk_load (pk->compiler, "pokefmt") != PK_OK)
> + errx (1, "pk_load() failed for pokefmt.pk");
> +
> + if (pk_decl_val (pk->compiler, "pokefmt_expr_printer") == PK_NULL)
> + errx (1, "pokefmt_expr_printer Poke function is not available");
> + if (pk_decl_val (pk->compiler, "pokefmt_exception_handler") == PK_NULL)
> + errx (1, "pokefmt_exception_handler Poke function is not available");
> +
> + /* Run user-provided Poke code (using command line args). */
> + for (size_t i = 0; i < pokefmt_opts.n_codes; ++i)
> + {
> + ret = pk_compile_buffer (pk->compiler, pokefmt_opts.codes[i], NULL,
> + &pexc);
> + if (ret != PK_OK)
> + errx (1,
> + "pk_compile_buffer() failed for Poke code %zu on command line "
> + "(%s)",
> + i, pokefmt_opts.codes[i]);
> + else if (pexc != PK_NULL)
> + {
> + default_exception_handler (pk, pexc);
> + errx (1,
> + "unhandled exception while running Poke code %zu on command "
> + "line (%s)",
> + i, pokefmt_opts.codes[i]);
> + }
> + }
> +}
> +
> +void
> +poke_free (struct poke *pk)
> +{
> + free (pk->filename);
> + pk_compiler_free (pk->compiler);
> +}
> +
> +static void
> +default_exception_handler (struct poke *poke, pk_val exc)
> +{
> + pk_val handler = pk_decl_val (poke->compiler, "pokefmt_exception_handler");
> +
> + assert (handler != PK_NULL);
> + if (pk_call (poke->compiler, handler, NULL, NULL, 1, exc) != PK_OK)
> + PK_UNREACHABLE ();
> +}
> +
> +static void
> +poke_stmt (struct poke *poke, const char *poke_src,
> + const struct source_range *r)
> +{
> + int res;
> + pk_val exc;
> +
> + assert (poke_src != NULL);
> +
> + res = pk_compile_buffer_with_loc (
> + poke->compiler, poke_src, poke->filename, r->line[0],
> + r->col[0] + /* Skip "%{" or "%(". */ 2, NULL, &exc);
> + if (res != PK_OK)
> + errx (
> + 1,
> + "pk_compile_buffer() failed for Poke code in range %d,%d-%d,%d:
> '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> + else if (exc != PK_NULL)
> + {
> + default_exception_handler (poke, exc);
> + errx (
> + 1,
> + "unhandled exception happend during execution of Poke code in
> range"
> + " %d,%d-%d,%d: '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> + }
> +}
> +
> +static void
> +poke_expr (struct poke *poke, const char *poke_src,
> + const struct source_range *r)
> +{
> + int res;
> + pk_val val, exc, printer, printer_ret, printer_exc;
> +
> + assert (poke_src != NULL);
> +
> + res = pk_compile_statement_with_loc (
> + poke->compiler, poke_src, poke->filename, r->line[0],
> + r->col[0] + /* Skip "%{" or "%(". */ 2, NULL, &val, &exc);
> + if (res != PK_OK)
> + errx (1,
> + "pk_compile_statement() failed for Poke expression in range "
> + "%d,%d-%d,%d: '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> + else if (exc != PK_NULL)
> + {
> + default_exception_handler (poke, exc);
> + errx (
> + 1,
> + "unhandled exception happend during execution of Poke code in
> range"
> + " %d,%d-%d,%d: '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> + }
> + else if (val == PK_NULL)
> + errx (1,
> + "expected a Poke expression, got a Poke statement in Poke code in "
> + "range %d,%d-%d,%d: '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> +
> + printer = pk_decl_val (poke->compiler, "pokefmt_expr_printer");
> + res = pk_call (poke->compiler, printer, &printer_ret, &printer_exc,
> + /*narg*/ 1, val);
> + if (res != PK_OK || printer_exc != PK_NULL)
> + {
> + default_exception_handler (poke, printer_exc);
> + errx (1,
> + "pk_call() failed for pokefmt_expr_printer during printing "
> + "the result of Poke expression in range %d,%d-%d,%d: '%s'",
> + r->line[0], r->col[0], r->line[1], r->col[1], poke_src);
> + }
> +}
> diff --git a/pokefmt/pokefmt.pk b/pokefmt/pokefmt.pk
> new file mode 100644
> index 00000000..c71a44ff
> --- /dev/null
> +++ b/pokefmt/pokefmt.pk
> @@ -0,0 +1,47 @@
> +/* pokefmt.pk - Poke support code for pokefmt application. */
> +
> +/* Copyright (C) 2023 Mohammad-Reza Nabipoor. */
> +
> +/* This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +/* Printer for Poke expressions (Poke code between "%(" and ")%".
> +
> + User can re-write this function to customize the expression printer. */
> +
> +fun pokefmt_expr_printer = (any val) void:
> +{
> + _pkl_print_any (val);
> +}
> +
> +/* Default exception handler.
> +
> + User can re-write this function to customize the default exception
> + handling. */
> +
> +fun pokefmt_exception_handler = (Exception exc) void:
> +{
> + if (exc.code != EC_exit && exc.code != EC_signal)
> + {
> + printf ("unhandled %<warning:%s exception%>\n",
> + exc.name == "" ? "unknown" : exc.name);
> +
> + if (exc.location != "" || exc.msg != "")
> + {
> + if (exc.location != "")
> + print (exc.location + " ");
> + print (exc.msg + "\n");
> + }
> + }
> +}
> diff --git a/run.in b/run.in
> index 83f2ebd7..1fe75f59 100644
> --- a/run.in
> +++ b/run.in
> @@ -36,6 +36,7 @@ POKECONFIGDIR=$b/libpoke
> POKEINFODIR=$s/doc
> POKEAPPDIR=$s/poke
> POKEDAPPDIR=$s/poked
> +POKEFMTAPPDIR=$s/pokefmt
> POKEPICKLESDIR=$s/pickles
> POKEMAPSDIR=$s/maps
> POKEDOCDIR=$s/doc
> diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am
> index a3fa534a..3ba92732 100644
> --- a/testsuite/Makefile.am
> +++ b/testsuite/Makefile.am
> @@ -36,6 +36,7 @@ if HAVE_DEJAGNU
> POKECMDSDIR="$(top_srcdir)/poke" \
> POKEDOCDIR="$(top_builddir)/doc" \
> POKE_LOAD_PATH="$(top_srcdir)/poke" \
> + POKEFMTAPPDIR="$(top_srcdir)/pokefmt" \
> $$runtest --tool $(DEJATOOL) --srcdir $${srcdir} --objdir
> $(builddir) \
> SHELL="$(SHELL)" \
> $(RUNTESTFLAGS) || exit $$1; \
> @@ -2798,6 +2799,7 @@ EXTRA_DIST = \
> poke.pktest/pktest-10.pk \
> poke.pktest/pktest-11.pk \
> poke.pktest/pktest-12.pk \
> + poke.pokefmt/pokefmt.exp \
> poke.pvm/pvm-insns-test.pk \
> poke.repl/repl.exp \
> poke.std/std.exp \
> diff --git a/testsuite/poke.pokefmt/pokefmt.exp
> b/testsuite/poke.pokefmt/pokefmt.exp
> new file mode 100644
> index 00000000..cd03efb2
> --- /dev/null
> +++ b/testsuite/poke.pokefmt/pokefmt.exp
> @@ -0,0 +1,115 @@
> +# pokefmt.exp - Tests for pokefmt application.
> +#
> +# Copyright (C) 2023 The poke authors
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, see <http://www.gnu.org/licenses/>.
> +
> +load_lib standard.exp
> +load_lib dejagnu.exp
> +
> +global POKEFMT
> +if ![info exists POKEFMT] {
> + set POKEFMT ${objdir}/../pokefmt/pokefmt
> +}
> +
> +set tmpdir /tmp
> +catch {
> + set tmpdir $::env(TMPDIR)
> +}
> +set SUBDIR [file join $tmpdir pokefmt-data.[pid]]
> +if {! [file exists $SUBDIR]} {
> + file mkdir $SUBDIR
> +}
> +
> +set timeout 3
> +
> +proc pokefmt_run {test input output} {
> + global POKEFMT
> + global SUBDIR
> +
> + set fin $SUBDIR/input
> + set fout $SUBDIR/output
> +
> + set in [open $fin w]
> + puts -nonewline $in $input
> + close $in
> +
> + exec $POKEFMT < $fin > $fout
> +
> + set out [open $fout r]
> + set actual_output [read $out]
> + close $out
> +
> + if {[string equal "$output" "$actual_output"]} {
> + pass "pokefmt $test"
> + } else {
> + fail "pokefmt $test"
> + verbose "expected:'$output' != actual:'$actual_output'" 1
> + }
> +}
> +
> +pokefmt_run 1 "Hi" "Hi"
> +pokefmt_run 2 "Hi{}Bye" "Hi{}Bye"
> +pokefmt_run 3 "Hi}%Bye" "Hi}%Bye"
> +pokefmt_run 4 "Hi{}%Bye" "Hi{}%Bye"
> +pokefmt_run 5 "Hi\\%{}%Bye" "Hi%{}%Bye"
> +pokefmt_run 6 "Hi%{}%Bye" "HiBye"
> +pokefmt_run 7 "Hi%{Bye" "Hi"
> +pokefmt_run 8 "Hi()Bye" "Hi()Bye"
> +pokefmt_run 9 "Hi)%Bye" "Hi)%Bye"
> +pokefmt_run 10 "Hi()%Bye" "Hi()%Bye"
> +pokefmt_run 11 "Hi\\%()%Bye" "Hi%()%Bye"
> +pokefmt_run 12 "Hi%(0)%Bye" "Hi0Bye"
> +pokefmt_run 13 "Hi%(Bye" "Hi"
> +pokefmt_run 14 {Hi%{print "-";}%Bye} "Hi-Bye"
> +pokefmt_run 15 {Hi%("-")%Bye} {Hi"-"Bye}
> +pokefmt_run 16 "Hi%()Bye" "Hi"
> +pokefmt_run 17 "%(0xff)%" "255"
> +pokefmt_run 18 "%(1 + 3 + 4)% + %(1)% = %(1+3+4+1)%" "8 + 1 = 9"
> +
> +pokefmt_run 19 {
> +#as: -march=rv32i
> +#objdump: -dr
> +%{
> + load riscv;
> + fun pokefmt_expr_printer = (any i32) void:
> + {
> + printf ("%u32x", i32 as uint<32>);
> + }
> +}%
> +.*:[ ]+file format .*
> +
> +
> +Disassembly of section .text:
> +
> +0+000 <target>:
> +#...
> +[ ]+40:[ ]+%(+(rv32_auipc :rd 0 :imm 0))%[ ]+auipc[ ]+zero,0x0
> +[ ]+44:[ ]+%(+(rv32_lw :rd 0 :rs1 0 :imm 0))%[ ]+lw[
> ]+zero,0\(zero\) # 0 .*
> +} {
> +#as: -march=rv32i
> +#objdump: -dr
> +
> +.*:[ ]+file format .*
> +
> +
> +Disassembly of section .text:
> +
> +0+000 <target>:
> +#...
> +[ ]+40:[ ]+00000017[ ]+auipc[ ]+zero,0x0
> +[ ]+44:[ ]+00002003[ ]+lw[ ]+zero,0\(zero\) # 0 .*
> +}
> +
> +file delete -force $SUBDIR
This is OK for master.
But please add yourself to HACKING as the maintainer of the new
subsystem.
Thanks.