poke-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]