[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] pokefmt: add template system to embed Poke code in files
From: |
Mohammad-Reza Nabipoor |
Subject: |
[PATCH] pokefmt: add template system to embed Poke code in files |
Date: |
Fri, 22 Sep 2023 20:27:30 +0330 |
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
+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
+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
--
2.42.0
- [PATCH] pokefmt: add template system to embed Poke code in files,
Mohammad-Reza Nabipoor <=